1/*
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#ifndef ANDROID_AUDIO_ERROR_LOG_H
18#define ANDROID_AUDIO_ERROR_LOG_H
19
20#ifdef __cplusplus
21
22#include <iomanip>
23#include <mutex>
24#include <sstream>
25#include <unistd.h>
26#include <vector>
27
28#include <audio_utils/clock.h>
29#include <utils/Errors.h>
30
31namespace android {
32
33/**
34 * ErrorLog captures audio errors codes, combining consecutive identical error codes
35 * (within a specified time) into a single entry (to reduce log spamming).
36 *
37 * The entry thus contains the number of consecutive error codes,
38 * together with the first time the error code occurs and the last time the error code occurs.
39 *
40 * The type T represents the error code type and is an int32_t for the C API.
41 */
42template <typename T>
43class ErrorLog {
44public:
45    /**
46     * \brief Creates an ErrorLog object
47     *
48     * \param entries           the length of error history.
49     * \param aggregateNs       the maximum time in nanoseconds between identical error codes
50     *                          to be aggregated into a single entry.
51     */
52    explicit ErrorLog(size_t entries, int64_t aggregateNs = 1000000000 /* one second */)
53        : mErrors(0)
54        , mIdx(0)
55        , mAggregateNs(aggregateNs)
56        , mEntries(entries)
57    {
58    }
59
60    /**
61     * \brief Adds new error code to the error log.
62     *
63     * Consecutive errors with the same code will be aggregated
64     * if they occur within aggregateNs.
65     *
66     * \param code              error code of type T.
67     * \param nowNs             current time in nanoseconds.
68     */
69    void log(const T &code, int64_t nowNs)
70    {
71        std::lock_guard<std::mutex> guard(mLock);
72
73        ++mErrors;
74
75        // Within mAggregateNs (1 second by default), aggregate error codes together.
76        if (code == mEntries[mIdx].mCode
77                && nowNs - mEntries[mIdx].mLastTime < mAggregateNs) {
78            mEntries[mIdx].mCount++;
79            mEntries[mIdx].mLastTime = nowNs;
80            return;
81        }
82
83        // Add new error entry.
84        if (++mIdx >= mEntries.size()) {
85            mIdx = 0;
86        }
87        mEntries[mIdx].setFirstError(code, nowNs);
88    }
89
90    /**
91     * \brief Dumps the log to a std::string.
92     * \param prefix            the prefix to use for each line
93     *                          (generally a null terminated string of spaces).
94     * \param lines             maximum number of lines to output (0 disables).
95     * \param limitNs           limit dump to data more recent than limitNs (0 disables).
96     * \return std::string of the dump.
97     */
98    std::string dumpToString(const char *prefix = "", size_t lines = 0, int64_t limitNs = 0) const
99    {
100        std::lock_guard<std::mutex> guard(mLock);
101
102        std::stringstream ss;
103        const size_t numberOfEntries = mEntries.size();
104        const size_t headerLines = 2;
105
106        if (lines == 0) {
107            lines = SIZE_MAX;
108        }
109        ss << prefix << "Errors: " << mErrors << "\n";
110
111        if (mErrors == 0 || lines <= headerLines) {
112            return ss.str();
113        }
114
115        lines = std::min(lines - headerLines, numberOfEntries);
116        // compute where to start dump log
117        ssize_t offset;
118        for (offset = 0; offset < (ssize_t)lines; ++offset) {
119            const auto &entry =
120                    mEntries[(mIdx + numberOfEntries - offset) % numberOfEntries];
121            if (entry.mCount == 0 || entry.mLastTime < limitNs) {
122                break;
123            }
124        }
125        if (offset > 0) {
126            offset--;
127            ss << prefix << " Code  Freq          First time           Last time\n";
128            for (; offset >= 0; --offset) {
129                const auto &entry =
130                        mEntries[(mIdx + numberOfEntries - offset) % numberOfEntries];
131
132                ss << prefix << std::setw(5) <<  entry.mCode
133                        << " " << std::setw(5) << entry.mCount
134                        << "  " << audio_utils_time_string_from_ns(entry.mFirstTime).time
135                        << "  " << audio_utils_time_string_from_ns(entry.mLastTime).time << "\n";
136            }
137        }
138        return ss.str();
139    }
140
141    /**
142     * \brief Dumps the log to a raw file descriptor.
143     * \param fd                file descriptor to use.
144     * \param prefix            the prefix to use for each line
145     *                          (generally a null terminated string of spaces).
146     * \param lines             maximum number of lines to output (0 disables).
147     * \param limitNs           limit dump to data more recent than limitNs (0 disables).
148     * \return
149     *   NO_ERROR on success or a negative number (-errno) on failure of write().
150     */
151    status_t dump(int fd, const char *prefix = "", size_t lines = 0, int64_t limitNs = 0) const
152    {
153        // thread safe but not necessarily serial with respect to concurrent dumps to the same fd.
154        const std::string s = dumpToString(prefix, lines, limitNs);
155        if (s.size() > 0 && write(fd, s.c_str(), s.size()) < 0) {
156            return -errno;
157        }
158        return NO_ERROR;
159    }
160
161    struct Entry {
162        Entry()
163            : mCode(0)
164            , mCount(0)
165            , mFirstTime(0)
166            , mLastTime(0)
167        {
168        }
169
170        // Initialize entry with code as the first error at the given time.
171        void setFirstError(T code, int64_t time) {
172            mCode = code;
173            mCount = 1;
174            mFirstTime = time;
175            mLastTime = time;
176        }
177
178        T mCode;            // error code
179        uint32_t mCount;    // number of consecutive errors of the same code.
180        int64_t mFirstTime; // first time of the error code.
181        int64_t mLastTime;  // last time of the error code.
182    };
183
184private:
185    mutable std::mutex mLock;     // monitor mutex
186    int64_t mErrors;              // total number of errors registered
187    size_t mIdx;                  // current index into mEntries (active)
188    const int64_t mAggregateNs;   // number of nanoseconds to aggregate consecutive error codes.
189    std::vector<Entry> mEntries;  // circular buffer of error entries.
190};
191
192} // namespace android
193
194#endif // __cplusplus
195
196// C API (see C++ API above for details)
197
198/** \cond */
199__BEGIN_DECLS
200/** \endcond */
201
202typedef struct error_log_t error_log_t;
203
204/**
205 * \brief Creates an error log object
206 *
207 * \param entries           the length of error history.
208 * \param aggregate_ns      the maximum time in nanoseconds between identical error codes
209 *                          to be aggregated into a single entry.
210 * \return the error log object or NULL on failure.
211 */
212error_log_t *error_log_create(size_t entries, int64_t aggregate_ns);
213
214/**
215 * \brief Adds new error code to the error log.
216 *
217 * Consecutive errors with the same code will be aggregated if
218 * they occur within aggregate_ns.
219 *
220 * \param error_log         object returned by create, if NULL nothing happens.
221 * \param code              error code of type T.
222 * \param now_ns            current time in nanoseconds.
223 */
224void error_log_log(error_log_t *error_log, int32_t code, int64_t now_ns);
225
226/**
227 * \brief Dumps the log to a raw file descriptor.
228 * \param error_log         object returned by create, if NULL nothing happens.
229 * \param prefix            the prefix to use for each line
230 *                          (generally a null terminated string of spaces).
231 * \param fd                file descriptor to use.
232 * \param lines             maximum number of lines to output (0 disables).
233 * \param limit_ns          limit dump to data more recent than limit_ns (0 disables).
234 * \return
235 *   NO_ERROR on success or a negative number (-errno) on failure of write().
236 *   if power_log is NULL, BAD_VALUE is returned.
237 */
238int error_log_dump(
239        error_log_t *error_log, int fd, const char *prefix, size_t lines, int64_t limit_ns);
240
241/**
242 * \brief Destroys the error log object.
243 *
244 * \param error_log         object returned by create, if NULL nothing happens.
245 */
246void error_log_destroy(error_log_t *error_log);
247
248/** \cond */
249__END_DECLS
250/** \endcond */
251
252#endif // !ANDROID_AUDIO_ERROR_LOG_H
253