blob: 88448b14a670c797381534282fdd4af1f550d243 [file] [log] [blame]
/*
* Copyright 2017 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.
*/
#ifndef ANDROID_AUDIO_ERROR_LOG_H
#define ANDROID_AUDIO_ERROR_LOG_H
#ifdef __cplusplus
#include <iomanip>
#include <mutex>
#include <sstream>
#include <unistd.h>
#include <vector>
#include <audio_utils/clock.h>
#include <utils/Errors.h>
namespace android {
/**
* ErrorLog captures audio errors codes, combining consecutive identical error codes
* (within a specified time) into a single entry (to reduce log spamming).
*
* The entry thus contains the number of consecutive error codes,
* together with the first time the error code occurs and the last time the error code occurs.
*
* The type T represents the error code type and is an int32_t for the C API.
*/
template <typename T>
class ErrorLog {
public:
/**
* \brief Creates an ErrorLog object
*
* \param entries the length of error history.
* \param aggregateNs the maximum time in nanoseconds between identical error codes
* to be aggregated into a single entry.
*/
explicit ErrorLog(size_t entries, int64_t aggregateNs = 1000000000 /* one second */)
: mErrors(0)
, mIdx(0)
, mAggregateNs(aggregateNs)
, mEntries(entries)
{
}
/**
* \brief Adds new error code to the error log.
*
* Consecutive errors with the same code will be aggregated
* if they occur within aggregateNs.
*
* \param code error code of type T.
* \param nowNs current time in nanoseconds.
*/
void log(const T &code, int64_t nowNs)
{
std::lock_guard<std::mutex> guard(mLock);
++mErrors;
// Within mAggregateNs (1 second by default), aggregate error codes together.
if (code == mEntries[mIdx].mCode
&& nowNs - mEntries[mIdx].mLastTime < mAggregateNs) {
mEntries[mIdx].mCount++;
mEntries[mIdx].mLastTime = nowNs;
return;
}
// Add new error entry.
if (++mIdx >= mEntries.size()) {
mIdx = 0;
}
mEntries[mIdx].setFirstError(code, nowNs);
}
/**
* \brief Dumps the log to a std::string.
* \param prefix the prefix to use for each line
* (generally a null terminated string of spaces).
* \param lines maximum number of lines to output (0 disables).
* \param limitNs limit dump to data more recent than limitNs (0 disables).
* \return std::string of the dump.
*/
std::string dumpToString(const char *prefix = "", size_t lines = 0, int64_t limitNs = 0) const
{
std::lock_guard<std::mutex> guard(mLock);
std::stringstream ss;
const size_t numberOfEntries = mEntries.size();
const size_t headerLines = 2;
if (lines == 0) {
lines = SIZE_MAX;
}
ss << prefix << "Errors: " << mErrors << "\n";
if (mErrors == 0 || lines <= headerLines) {
return ss.str();
}
lines = std::min(lines - headerLines, numberOfEntries);
// compute where to start dump log
ssize_t offset;
for (offset = 0; offset < (ssize_t)lines; ++offset) {
const auto &entry =
mEntries[(mIdx + numberOfEntries - offset) % numberOfEntries];
if (entry.mCount == 0 || entry.mLastTime < limitNs) {
break;
}
}
if (offset > 0) {
offset--;
ss << prefix << " Code Freq First time Last time\n";
for (; offset >= 0; --offset) {
const auto &entry =
mEntries[(mIdx + numberOfEntries - offset) % numberOfEntries];
ss << prefix << std::setw(5) << entry.mCode
<< " " << std::setw(5) << entry.mCount
<< " " << audio_utils_time_string_from_ns(entry.mFirstTime).time
<< " " << audio_utils_time_string_from_ns(entry.mLastTime).time << "\n";
}
}
return ss.str();
}
/**
* \brief Dumps the log to a raw file descriptor.
* \param fd file descriptor to use.
* \param prefix the prefix to use for each line
* (generally a null terminated string of spaces).
* \param lines maximum number of lines to output (0 disables).
* \param limitNs limit dump to data more recent than limitNs (0 disables).
* \return
* NO_ERROR on success or a negative number (-errno) on failure of write().
*/
status_t dump(int fd, const char *prefix = "", size_t lines = 0, int64_t limitNs = 0) const
{
// thread safe but not necessarily serial with respect to concurrent dumps to the same fd.
const std::string s = dumpToString(prefix, lines, limitNs);
if (s.size() > 0 && write(fd, s.c_str(), s.size()) < 0) {
return -errno;
}
return NO_ERROR;
}
struct Entry {
Entry()
: mCode(0)
, mCount(0)
, mFirstTime(0)
, mLastTime(0)
{
}
// Initialize entry with code as the first error at the given time.
void setFirstError(T code, int64_t time) {
mCode = code;
mCount = 1;
mFirstTime = time;
mLastTime = time;
}
T mCode; // error code
uint32_t mCount; // number of consecutive errors of the same code.
int64_t mFirstTime; // first time of the error code.
int64_t mLastTime; // last time of the error code.
};
private:
mutable std::mutex mLock; // monitor mutex
int64_t mErrors; // total number of errors registered
size_t mIdx; // current index into mEntries (active)
const int64_t mAggregateNs; // number of nanoseconds to aggregate consecutive error codes.
std::vector<Entry> mEntries; // circular buffer of error entries.
};
} // namespace android
#endif // __cplusplus
// C API (see C++ API above for details)
/** \cond */
__BEGIN_DECLS
/** \endcond */
typedef struct error_log_t error_log_t;
/**
* \brief Creates an error log object
*
* \param entries the length of error history.
* \param aggregate_ns the maximum time in nanoseconds between identical error codes
* to be aggregated into a single entry.
* \return the error log object or NULL on failure.
*/
error_log_t *error_log_create(size_t entries, int64_t aggregate_ns);
/**
* \brief Adds new error code to the error log.
*
* Consecutive errors with the same code will be aggregated if
* they occur within aggregate_ns.
*
* \param error_log object returned by create, if NULL nothing happens.
* \param code error code of type T.
* \param now_ns current time in nanoseconds.
*/
void error_log_log(error_log_t *error_log, int32_t code, int64_t now_ns);
/**
* \brief Dumps the log to a raw file descriptor.
* \param error_log object returned by create, if NULL nothing happens.
* \param prefix the prefix to use for each line
* (generally a null terminated string of spaces).
* \param fd file descriptor to use.
* \param lines maximum number of lines to output (0 disables).
* \param limit_ns limit dump to data more recent than limit_ns (0 disables).
* \return
* NO_ERROR on success or a negative number (-errno) on failure of write().
* if power_log is NULL, BAD_VALUE is returned.
*/
int error_log_dump(
error_log_t *error_log, int fd, const char *prefix, size_t lines, int64_t limit_ns);
/**
* \brief Destroys the error log object.
*
* \param error_log object returned by create, if NULL nothing happens.
*/
void error_log_destroy(error_log_t *error_log);
/** \cond */
__END_DECLS
/** \endcond */
#endif // !ANDROID_AUDIO_ERROR_LOG_H