From 031b9a4d568fc2d2e480fbc9b1cb3ebf4dd5bef7 Mon Sep 17 00:00:00 2001 From: Mark Kendall Date: Fri, 3 Jun 2011 15:23:54 +0800 Subject: [PATCH] RingBuffer: Add bitrate monitoring code. - enable monitoring with EnableBitrateMonitor(true) - poll for data via GetDecoderRate, GetStorageRate, GetAvailableBuffer - disable monitoring with EnableBitrateMonitor(false) The decoder rate is the rate at which the decoder has requested data from the buffer over the previous second. The storage rate is the average rate at which data has been delivered to the buffer in the previous second. The available buffer is the % of the buffer that is available for the decoder. N.B. the storage rate can be misleading. For dvd and bluray playback the rate shown is the speed of retrieving data from the libdvdnav/libbluray object - there is another ringbuffer layer that performs the actual reads from local or remote storage. For network mounted drives and http:/ streaming, there is additional buffering that may suggest that read speeds are in excess of the actual network capabilities. --- mythtv/libs/libmythtv/ringbuffer.cpp | 135 ++++++++++++++++++++++++++- mythtv/libs/libmythtv/ringbuffer.h | 16 ++++ 2 files changed, 146 insertions(+), 5 deletions(-) diff --git a/mythtv/libs/libmythtv/ringbuffer.cpp b/mythtv/libs/libmythtv/ringbuffer.cpp index 26fda35ddd5..e1f5c95293f 100644 --- a/mythtv/libs/libmythtv/ringbuffer.cpp +++ b/mythtv/libs/libmythtv/ringbuffer.cpp @@ -185,7 +185,8 @@ RingBuffer::RingBuffer(void) : readblocksize(CHUNK), wanttoread(0), numfailures(0), commserror(false), oldfile(false), livetvchain(NULL), - ignoreliveeof(false), readAdjust(0) + ignoreliveeof(false), readAdjust(0), + bitrateMonitorEnabled(false) { { QMutexLocker locker(&subExtLock); @@ -752,14 +753,14 @@ void RingBuffer::run(void) read_return = safe_read(readAheadBuffer + rbwpos, totfree); int sr_elapsed = sr_timer.elapsed(); + uint64_t bps = !sr_elapsed ? 1000000001 : + (uint64_t)(((double)read_return * 8000.0) / (double)sr_elapsed); VERBOSE(VB_FILE, LOC + QString("safe_read(...@%1, %2) -> %3, took %4 ms %5") .arg(rbwpos).arg(totfree).arg(read_return) .arg(sr_elapsed) - .arg(!sr_elapsed ? "" : - QString("(%1Mbps)").arg(((float)read_return * - (8000.0 / (float)sr_elapsed)) / 1048576))); - + .arg(QString("(%1Mbps)").arg((double)bps / 1000000.0))); + UpdateStorageRate(bps); rbwlock.unlock(); } @@ -977,7 +978,13 @@ int RingBuffer::ReadDirect(void *buf, int count, bool peek) poslock.unlock(); } + MythTimer timer; + timer.start(); int ret = safe_read(buf, count); + int elapsed = timer.elapsed(); + uint64_t bps = !elapsed ? 1000000001 : + (uint64_t)(((float)ret * 8000.0) / (float)elapsed); + UpdateStorageRate(bps); poslock.lockForWrite(); if (ignorereadpos >= 0 && ret > 0) @@ -1168,9 +1175,127 @@ int RingBuffer::Read(void *buf, int count) readpos += ret; poslock.unlock(); } + + UpdateDecoderRate(ret); return ret; } +QString RingBuffer::BitrateToString(uint64_t rate) +{ + QString msg; + float bitrate; + int range = 0; + if (rate < 1) + { + return "-"; + } + else if (rate > 1000000000) + { + return QObject::tr(">1Gbps"); + } + else if (rate >= 1000000) + { + msg = QObject::tr("%1Mbps"); + bitrate = (float)rate / (1000000.0); + range = 1; + } + else if (rate >= 1000) + { + msg = QObject::tr("%1Kbps"); + bitrate = (float)rate / 1000.0; + } + else + { + msg = QObject::tr("%1bps"); + bitrate = (float)rate; + } + return msg.arg(bitrate, 0, 'f', range); +} + +QString RingBuffer::GetDecoderRate(void) +{ + return BitrateToString(UpdateDecoderRate()); +} + +QString RingBuffer::GetStorageRate(void) +{ + return BitrateToString(UpdateStorageRate()); +} + +QString RingBuffer::GetAvailableBuffer(void) +{ + int avail = (rbwpos >= rbrpos) ? rbwpos - rbrpos : kBufferSize - rbrpos + rbwpos; + return QString("%1%").arg((int)(((float)avail / (float)kBufferSize) * 100.0)); +} + +uint64_t RingBuffer::UpdateDecoderRate(uint64_t latest) +{ + if (!bitrateMonitorEnabled) + return 0; + + static QDateTime epoch = QDateTime(QDate(1971, 1, 1)); + QDateTime now = QDateTime::currentDateTime(); + qint64 age = epoch.msecsTo(now); + qint64 oldest = age - 1000; + + decoderReadLock.lock(); + uint64_t total = 0; + QMutableMapIterator it(decoderReads); + while (it.hasNext()) + { + it.next(); + if (it.key() < oldest) + it.remove(); + else + total += it.value(); + } + + if (latest) + decoderReads.insert(age, latest); + + uint64_t average = (uint64_t)((double)total * 8.0); + decoderReadLock.unlock(); + + VERBOSE(VB_FILE, LOC + QString("Decoder read speed: %1 %2") + .arg(average).arg(decoderReads.size())); + return average; +} + +uint64_t RingBuffer::UpdateStorageRate(uint64_t latest) +{ + if (!bitrateMonitorEnabled) + return 0; + + static QDateTime epoch = QDateTime(QDate(1971, 1, 1)); + QDateTime now = QDateTime::currentDateTime(); + qint64 age = epoch.msecsTo(now); + qint64 oldest = age - 1000; + + storageReadLock.lock(); + uint64_t total = 0; + QMutableMapIterator it(storageReads); + while (it.hasNext()) + { + it.next(); + if (it.key() < oldest) + it.remove(); + else + total += it.value(); + } + + if (latest) + storageReads.insert(age, latest); + + int size = storageReads.size(); + storageReadLock.unlock(); + + uint64_t average = size ? (uint64_t)(((double)total) / (double)size) : 0; + + VERBOSE(VB_FILE, LOC + QString("Average storage read speed: %1 %2") + .arg(average).arg(storageReads.size())); + return average; +} + /** \fn RingBuffer::Write(const void*, uint) * \brief Writes buffer to ThreadedFileWriter::Write(const void*,uint) * \return Bytes written, or -1 on error. diff --git a/mythtv/libs/libmythtv/ringbuffer.h b/mythtv/libs/libmythtv/ringbuffer.h index 69a5c0b573a..1532e8416f4 100644 --- a/mythtv/libs/libmythtv/ringbuffer.h +++ b/mythtv/libs/libmythtv/ringbuffer.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "mythconfig.h" @@ -45,6 +46,7 @@ class MTV_PUBLIC RingBuffer : protected QThread void SetOldFile(bool is_old); void UpdateRawBitrate(uint rawbitrate); void UpdatePlaySpeed(float playspeed); + void EnableBitrateMonitor(bool enable) { bitrateMonitorEnabled = enable; } // Gets QString GetFilename(void) const; @@ -55,6 +57,9 @@ class MTV_PUBLIC RingBuffer : protected QThread bool isPaused(void) const; /// \brief Returns how far into the file we have read. virtual long long GetReadPosition(void) const = 0; + QString GetDecoderRate(void); + QString GetStorageRate(void); + QString GetAvailableBuffer(void); long long GetWritePosition(void) const; /// \brief Returns the size of the file we are reading/writing, /// or -1 if the query fails. @@ -149,6 +154,10 @@ class MTV_PUBLIC RingBuffer : protected QThread void ResetReadAhead(long long newinternal); void KillReadAheadThread(void); + static QString BitrateToString(uint64_t rate); + uint64_t UpdateDecoderRate(uint64_t latest = 0); + uint64_t UpdateStorageRate(uint64_t latest = 0); + protected: mutable QReadWriteLock poslock; long long readpos; // protected by poslock @@ -202,6 +211,13 @@ class MTV_PUBLIC RingBuffer : protected QThread long long readAdjust; // protected by rwlock + // bitrate monitors + bool bitrateMonitorEnabled; + QMutex decoderReadLock; + QMap decoderReads; + QMutex storageReadLock; + QMap storageReads; + // note 1: numfailures is modified with only a read lock in the // read ahead thread, but this is safe since all other places // that use it are protected by a write lock. But this is a