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 
31 namespace 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  */
42 template <typename T>
43 class ErrorLog {
44 public:
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      */
log(const T & code,int64_t nowNs)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 {
EntryEntry162         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.
setFirstErrorEntry171         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 
184 private:
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 
202 typedef 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  */
212 error_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  */
224 void 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  */
238 int 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  */
246 void 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