/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "AudioWatchdog"
//#define LOG_NDEBUG 0
#include "Configuration.h"
#include <utils/Log.h>
#include "AudioWatchdog.h"
#ifdef AUDIO_WATCHDOG
namespace android {
void AudioWatchdogDump::dump(int fd)
{
char buf[32];
if (mMostRecent != 0) {
// includes NUL terminator
ctime_r(&mMostRecent, buf);
} else {
strcpy(buf, "N/A\n");
}
fdprintf(fd, "Watchdog: underruns=%u, logs=%u, most recent underrun log at %s",
mUnderruns, mLogs, buf);
}
bool AudioWatchdog::threadLoop()
{
{
AutoMutex _l(mMyLock);
if (mPaused) {
mMyCond.wait(mMyLock);
// ignore previous timestamp after resume()
mOldTsValid = false;
// force an immediate log on first underrun after resume()
mLogTs.tv_sec = MIN_TIME_BETWEEN_LOGS_SEC;
mLogTs.tv_nsec = 0;
// caller will check for exitPending()
return true;
}
}
struct timespec newTs;
int rc = clock_gettime(CLOCK_MONOTONIC, &newTs);
if (rc != 0) {
pause();
return false;
}
if (!mOldTsValid) {
mOldTs = newTs;
mOldTsValid = true;
return true;
}
time_t sec = newTs.tv_sec - mOldTs.tv_sec;
long nsec = newTs.tv_nsec - mOldTs.tv_nsec;
if (nsec < 0) {
--sec;
nsec += 1000000000;
}
mOldTs = newTs;
// cycleNs is same as sec*1e9 + nsec, but limited to about 4 seconds
uint32_t cycleNs = nsec;
if (sec > 0) {
if (sec < 4) {
cycleNs += sec * 1000000000;
} else {
cycleNs = 4000000000u;
}
}
mLogTs.tv_sec += sec;
if ((mLogTs.tv_nsec += nsec) >= 1000000000) {
mLogTs.tv_sec++;
mLogTs.tv_nsec -= 1000000000;
}
if (cycleNs > mMaxCycleNs) {
mDump->mUnderruns = ++mUnderruns;
if (mLogTs.tv_sec >= MIN_TIME_BETWEEN_LOGS_SEC) {
mDump->mLogs = ++mLogs;
mDump->mMostRecent = time(NULL);
ALOGW("Insufficient CPU for load: expected=%.1f actual=%.1f ms; underruns=%u logs=%u",
mPeriodNs * 1e-6, cycleNs * 1e-6, mUnderruns, mLogs);
mLogTs.tv_sec = 0;
mLogTs.tv_nsec = 0;
}
}
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = mPeriodNs;
rc = nanosleep(&req, NULL);
if (!((rc == 0) || (rc == -1 && errno == EINTR))) {
pause();
return false;
}
return true;
}
void AudioWatchdog::requestExit()
{
// must be in this order to avoid a race condition
Thread::requestExit();
resume();
}
void AudioWatchdog::pause()
{
AutoMutex _l(mMyLock);
mPaused = true;
}
void AudioWatchdog::resume()
{
AutoMutex _l(mMyLock);
if (mPaused) {
mPaused = false;
mMyCond.signal();
}
}
void AudioWatchdog::setDump(AudioWatchdogDump *dump)
{
mDump = dump != NULL ? dump : &mDummyDump;
}
} // namespace android
#endif // AUDIO_WATCHDOG