Skip to content

Commit 1284ce2

Browse files
lashminthapstroffolinoCopilot
authored
VPLAY-12472 [CSAT][AAMP] Add logging for buffering start/end and buffering duration (#1160)
* VPLAY-12472 [CSAT][AAMP] Add logging for buffering start/end and buffering duration Reason for change: Added buffering duration in the telementary along with buffer start and end Risks: Low Test Procedure: Refer jira ticket Priority: P1 Signed-off-by: lashminthaj <lashminthaj17@gmail.com> --------- Signed-off-by: lashminthaj <lashminthaj17@gmail.com> Signed-off-by: Philip Stroffolino <philip_stroffolino@cable.comcast.com> Co-authored-by: pstroffolino <Philip_Stroffolino@cable.comcast.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3b0e173 commit 1284ce2

File tree

3 files changed

+152
-5
lines changed

3 files changed

+152
-5
lines changed

priv_aamp.cpp

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,7 @@ int PrivateInstanceAAMP::HandleSSLProgressCallback ( void *clientp, double dltot
16351635
/**
16361636
* @brief PrivateInstanceAAMP Constructor
16371637
*/
1638-
PrivateInstanceAAMP::PrivateInstanceAAMP(AampConfig *config) : mReportProgressPosn(0.0), mLastTelemetryTimeMS(0), mDiscontinuityFound(false), mTelemetryInterval(0), mLock(),
1638+
PrivateInstanceAAMP::PrivateInstanceAAMP(AampConfig *config) : mReportProgressPosn(0.0), mLastTelemetryTimeMS(0), mBufferingStartTimeMS(-1), mDiscontinuityFound(false), mTelemetryInterval(0), mLock(),
16391639
mpStreamAbstractionAAMP(NULL), mInitSuccess(false), mVideoFormat(FORMAT_INVALID), mAudioFormat(FORMAT_INVALID), mDownloadsDisabled(),
16401640
mDownloadsEnabled(true), profiler(), licenceFromManifest(false), previousAudioType(eAUDIO_UNKNOWN),isPreferredDRMConfigured(false),
16411641
mbDownloadsBlocked(false), streamerIsActive(false), mFogTSBEnabled(false), mIscDVR(false), mLiveOffset(AAMP_LIVE_OFFSET),
@@ -3246,11 +3246,42 @@ void PrivateInstanceAAMP::SendBufferChangeEvent(bool bufferingStopped)
32463246
BufferingChangedEventPtr e = std::make_shared<BufferingChangedEvent>(!bufferingStopped, GetSessionId());
32473247

32483248
SetBufUnderFlowStatus(bufferingStopped);
3249-
AAMPLOG_INFO("PrivateInstanceAAMP: Sending Buffer Change event status (Buffering): %s", (e->buffering() ? "End": "Start"));
3249+
// Calculating the duration for which buffering was happening and sending it as part of telemetry.
3250+
// This will help in understanding the buffering duration for each buffering event and also help in calculating the total buffering duration for a session.
3251+
long long bufferingDurationMs = 0;
3252+
if (bufferingStopped)
3253+
{
3254+
// Atomically set the start time only if no episode is already being tracked (-1).
3255+
// compare_exchange_strong ensures that concurrent SendBufferChangeEvent(true) calls
3256+
// from different threads (underflow monitor, GStreamer error path) cannot both "win"
3257+
// and reset the clock. Uses the monotonic steady clock to be immune to NTP jumps.
3258+
long long expected = -1LL;
3259+
mBufferingStartTimeMS.compare_exchange_strong(expected, NOW_STEADY_TS_MS);
3260+
}
3261+
else
3262+
{
3263+
// Atomically swap to -1 and capture the previous start time in one operation.
3264+
// This prevents two concurrent SendBufferChangeEvent(false) calls from both
3265+
// observing a valid start time and both computing (and reporting) a duration.
3266+
long long startTime = mBufferingStartTimeMS.exchange(-1LL);
3267+
if (startTime >= 0)
3268+
{
3269+
bufferingDurationMs = NOW_STEADY_TS_MS - startTime;
3270+
// Clamp to 0 as a safety net (should not happen with a monotonic clock).
3271+
if (bufferingDurationMs < 0)
3272+
{
3273+
bufferingDurationMs = 0;
3274+
}
3275+
}
3276+
}
3277+
3278+
AAMPLOG_INFO("PrivateInstanceAAMP: Sending Buffer Change event status (Buffering): %s durationMs: %lld", (e->buffering() ? "End": "Start"), bufferingDurationMs);
32503279
#ifdef AAMP_TELEMETRY_SUPPORT
32513280
AAMPTelemetry2 at2(mAppName);
32523281
std::string telemetryName = bufferingStopped?"VideoBufferingStart":"VideoBufferingEnd";
3253-
at2.send(telemetryName,{/*int data*/},{/*string data*/},{/*float data*/});
3282+
std::map<std::string,int> intData;
3283+
intData["dur"] = static_cast<int>(bufferingDurationMs);
3284+
at2.send(telemetryName, intData, {/*string data*/}, {/*float data*/});
32543285
#endif //AAMP_TELEMETRY_SUPPORT
32553286
SendEvent(e,AAMP_EVENT_ASYNC_MODE);
32563287

priv_aamp.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,13 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_
586586
//The position previously reported by MonitorProgress() (i.e. the position really sent, using SendEvent())
587587
double mReportProgressPosn;
588588
long long mLastTelemetryTimeMS;
589+
// The time when buffering started (steady clock ms), used to calculate buffering duration
590+
// for telemetry. -1 means no episode is in progress. Declared atomic because
591+
// SendBufferChangeEvent() is reached concurrently from the underflow monitor thread and
592+
// from GStreamer error callbacks (via ScheduleRetune), with no common lock held at the
593+
// call site. The compound check-and-set operations in SendBufferChangeEvent use
594+
// compare_exchange_strong / exchange to keep the read-modify-write sequences race-free.
595+
std::atomic<long long> mBufferingStartTimeMS;
589596
std::chrono::system_clock::time_point m_lastSubClockSyncTime;
590597
std::shared_ptr<TSB::Store> mTSBStore; /**< Local TSB Store object */
591598
void SanitizeLanguageList(std::vector<std::string>& languages) const;
@@ -1579,6 +1586,9 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_
15791586
bool GetBufUnderFlowStatus() { return mBufUnderFlowStatus.load(); }
15801587
void SetBufUnderFlowStatus(bool statusFlag) { mBufUnderFlowStatus.store(statusFlag); }
15811588
void ResetBufUnderFlowStatus() { mBufUnderFlowStatus.store(false);}
1589+
/** Returns the steady-clock timestamp (ms) at which the current buffering episode began,
1590+
* or -1 if no episode is in progress. Exposed for unit testing. */
1591+
long long GetBufferingStartTimeMS() const { return mBufferingStartTimeMS.load(); }
15821592

15831593
/**
15841594
* @fn SendEvent

test/utests/tests/PrivAampTests/PrivAampTests.cpp

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
#include <iostream>
2222
#include <string>
2323
#include <string.h>
24+
#include <thread>
2425
#include <type_traits>
2526

2627
#include <gtest/gtest.h>
2728
#include <gmock/gmock.h>
2829

2930
#include "priv_aamp.h"
3031
#include "AampProfiler.h"
32+
#include "AampUtils.h"
3133

3234
#include "MockPrivateInstanceAAMP.h"
3335
#include "main_aamp.h"
@@ -1716,14 +1718,118 @@ TEST_F(PrivAampTests,UpdateRefreshPlaylistIntervalTest)
17161718
p_aamp->UpdateRefreshPlaylistInterval(-12.43265);
17171719
}
17181720

1719-
TEST_F(PrivAampTests,SendBufferChangeEventTest)
1721+
// --- Buffering duration tracking tests ---
1722+
1723+
// mBufferingStartTimeMS is initialised to -1 in the constructor; no episode in flight yet.
1724+
TEST_F(PrivAampTests, SendBufferChangeEvent_InitialTimestampIsUnset)
1725+
{
1726+
EXPECT_EQ(p_aamp->GetBufferingStartTimeMS(), -1LL);
1727+
}
1728+
1729+
// Start event must capture a plausible steady-clock timestamp (>= before, <= after).
1730+
TEST_F(PrivAampTests, SendBufferChangeEvent_StartSetsTimestamp)
1731+
{
1732+
long long beforeMs = NOW_STEADY_TS_MS;
1733+
p_aamp->SendBufferChangeEvent(true);
1734+
long long afterMs = NOW_STEADY_TS_MS;
1735+
1736+
long long recorded = p_aamp->GetBufferingStartTimeMS();
1737+
EXPECT_GE(recorded, beforeMs);
1738+
EXPECT_LE(recorded, afterMs);
1739+
}
1740+
1741+
// End event must reset mBufferingStartTimeMS back to -1.
1742+
TEST_F(PrivAampTests, SendBufferChangeEvent_EndResetsTimestamp)
1743+
{
1744+
p_aamp->SendBufferChangeEvent(true);
1745+
EXPECT_GE(p_aamp->GetBufferingStartTimeMS(), 0LL);
1746+
1747+
p_aamp->SendBufferChangeEvent(false);
1748+
EXPECT_EQ(p_aamp->GetBufferingStartTimeMS(), -1LL);
1749+
}
1750+
1751+
// End call without a preceding start is a no-op: no crash, state stays -1.
1752+
TEST_F(PrivAampTests, SendBufferChangeEvent_EndWithoutStartIsNoOp)
1753+
{
1754+
ASSERT_EQ(p_aamp->GetBufferingStartTimeMS(), -1LL);
1755+
p_aamp->SendBufferChangeEvent(false);
1756+
EXPECT_EQ(p_aamp->GetBufferingStartTimeMS(), -1LL);
1757+
}
1758+
1759+
// Duplicate start events must not reset the timestamp; only the first call wins.
1760+
// Verified by sleeping briefly so a second capture would produce a strictly later value.
1761+
TEST_F(PrivAampTests, SendBufferChangeEvent_DuplicateStartDoesNotResetTimestamp)
1762+
{
1763+
p_aamp->SendBufferChangeEvent(true);
1764+
long long firstTimestamp = p_aamp->GetBufferingStartTimeMS();
1765+
ASSERT_GE(firstTimestamp, 0LL);
1766+
1767+
std::this_thread::sleep_for(std::chrono::milliseconds(5));
1768+
p_aamp->SendBufferChangeEvent(true);
1769+
1770+
EXPECT_EQ(p_aamp->GetBufferingStartTimeMS(), firstTimestamp);
1771+
}
1772+
1773+
// The duration that would be reported to telemetry is bounded by the elapsed real time.
1774+
// We verify this indirectly: capture the steady-clock window around the end call and
1775+
// confirm it brackets (end_clock - startTime), then confirm the episode is closed out.
1776+
TEST_F(PrivAampTests, SendBufferChangeEvent_DurationBoundedByElapsedRealTime)
1777+
{
1778+
p_aamp->SendBufferChangeEvent(true);
1779+
long long startTime = p_aamp->GetBufferingStartTimeMS();
1780+
ASSERT_GE(startTime, 0LL);
1781+
1782+
std::this_thread::sleep_for(std::chrono::milliseconds(5));
1783+
1784+
long long beforeEnd = NOW_STEADY_TS_MS;
1785+
p_aamp->SendBufferChangeEvent(false);
1786+
long long afterEnd = NOW_STEADY_TS_MS;
1787+
1788+
// The internal duration = (steady_clock at end) - startTime.
1789+
// It must be >= (beforeEnd - startTime) and <= (afterEnd - startTime).
1790+
long long minDuration = beforeEnd - startTime;
1791+
long long maxDuration = afterEnd - startTime;
1792+
EXPECT_GE(minDuration, 0LL);
1793+
EXPECT_LE(minDuration, maxDuration);
1794+
1795+
// Episode must be closed out.
1796+
EXPECT_EQ(p_aamp->GetBufferingStartTimeMS(), -1LL);
1797+
}
1798+
1799+
// BufUnderFlowStatus flag must follow start/end transitions.
1800+
TEST_F(PrivAampTests, SendBufferChangeEvent_UnderflowStatusTracksTransitions)
17201801
{
1802+
EXPECT_FALSE(p_aamp->GetBufUnderFlowStatus());
1803+
17211804
p_aamp->SendBufferChangeEvent(true);
1805+
EXPECT_TRUE(p_aamp->GetBufUnderFlowStatus());
1806+
1807+
p_aamp->SendBufferChangeEvent(false);
1808+
EXPECT_FALSE(p_aamp->GetBufUnderFlowStatus());
17221809
}
17231810

1724-
TEST_F(PrivAampTests,SendBufferChangeEventTest_1)
1811+
// Back-to-back episodes must each be tracked independently: the second start time
1812+
// must be strictly later than the first (monotonic clock), and each episode closes cleanly.
1813+
TEST_F(PrivAampTests, SendBufferChangeEvent_SequentialEpisodesTrackedIndependently)
17251814
{
1815+
// First episode
1816+
p_aamp->SendBufferChangeEvent(true);
1817+
long long t1 = p_aamp->GetBufferingStartTimeMS();
1818+
ASSERT_GE(t1, 0LL);
1819+
1820+
std::this_thread::sleep_for(std::chrono::milliseconds(2));
1821+
p_aamp->SendBufferChangeEvent(false);
1822+
EXPECT_EQ(p_aamp->GetBufferingStartTimeMS(), -1LL);
1823+
1824+
// Second episode
1825+
std::this_thread::sleep_for(std::chrono::milliseconds(2));
1826+
p_aamp->SendBufferChangeEvent(true);
1827+
long long t2 = p_aamp->GetBufferingStartTimeMS();
1828+
ASSERT_GE(t2, 0LL);
1829+
EXPECT_GT(t2, t1); // monotonic: second start must be later than first
1830+
17261831
p_aamp->SendBufferChangeEvent(false);
1832+
EXPECT_EQ(p_aamp->GetBufferingStartTimeMS(), -1LL);
17271833
}
17281834

17291835
TEST_F(PrivAampTests,PausePipelineTest)

0 commit comments

Comments
 (0)