blob: aa678baa69e68e1b3e8f026b8504072a6edf7758 [file] [log] [blame]
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -07001/*
2 * Copyright (C) 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#define LOG_TAG "ReportPerformance"
Eric Tanace588c2018-09-12 11:44:43 -070018//#define LOG_NDEBUG 0
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070019
20#include <fstream>
21#include <iostream>
Eric Tanfefe3162018-09-07 10:09:11 -070022#include <memory>
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070023#include <queue>
24#include <stdarg.h>
25#include <stdint.h>
26#include <stdio.h>
27#include <string.h>
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -070028#include <sstream>
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070029#include <sys/prctl.h>
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -070030#include <sys/time.h>
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070031#include <utility>
Eric Tanfefe3162018-09-07 10:09:11 -070032#include <json/json.h>
Ray Essickf27e9872019-12-07 06:28:46 -080033#include <media/MediaMetricsItem.h>
Eric Tanace588c2018-09-12 11:44:43 -070034#include <media/nblog/Events.h>
Glenn Kasten8589ce72017-09-08 17:03:42 -070035#include <media/nblog/PerformanceAnalysis.h>
36#include <media/nblog/ReportPerformance.h>
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070037#include <utils/Log.h>
38#include <utils/String8.h>
39
40namespace android {
Eric Tand6eee712018-09-07 10:58:19 -070041namespace ReportPerformance {
42
Eric Tan0513b5d2018-09-17 10:32:48 -070043static std::unique_ptr<Json::Value> dumpToJson(const PerformanceData& data)
Eric Tanfefe3162018-09-07 10:09:11 -070044{
45 std::unique_ptr<Json::Value> rootPtr = std::make_unique<Json::Value>(Json::objectValue);
46 Json::Value& root = *rootPtr;
Eric Tan0513b5d2018-09-17 10:32:48 -070047 root["ioHandle"] = data.threadInfo.id;
48 root["type"] = NBLog::threadTypeToString(data.threadInfo.type);
49 root["frameCount"] = (Json::Value::Int)data.threadParams.frameCount;
50 root["sampleRate"] = (Json::Value::Int)data.threadParams.sampleRate;
Eric Tanfefe3162018-09-07 10:09:11 -070051 root["workMsHist"] = data.workHist.toString();
52 root["latencyMsHist"] = data.latencyHist.toString();
53 root["warmupMsHist"] = data.warmupHist.toString();
54 root["underruns"] = (Json::Value::Int64)data.underruns;
55 root["overruns"] = (Json::Value::Int64)data.overruns;
56 root["activeMs"] = (Json::Value::Int64)ns2ms(data.active);
57 root["durationMs"] = (Json::Value::Int64)ns2ms(systemTime() - data.start);
58 return rootPtr;
59}
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070060
Eric Tan0513b5d2018-09-17 10:32:48 -070061static std::string dumpHistogramsToString(const PerformanceData& data)
62{
63 std::stringstream ss;
64 ss << "==========================================\n";
65 ss << "Thread type=" << NBLog::threadTypeToString(data.threadInfo.type)
66 << " handle=" << data.threadInfo.id
67 << " sampleRate=" << data.threadParams.sampleRate
68 << " frameCount=" << data.threadParams.frameCount << "\n";
69 ss << " Thread work times in ms:\n" << data.workHist.asciiArtString(4 /*indent*/);
70 ss << " Thread latencies in ms:\n" << data.latencyHist.asciiArtString(4 /*indent*/);
71 ss << " Thread warmup times in ms:\n" << data.warmupHist.asciiArtString(4 /*indent*/);
72 return ss.str();
73}
74
75void dumpJson(int fd, const std::map<int, PerformanceData>& threadDataMap)
76{
77 if (fd < 0) {
78 return;
79 }
80
81 Json::Value root(Json::arrayValue);
82 for (const auto& item : threadDataMap) {
83 const ReportPerformance::PerformanceData& data = item.second;
84 // Skip threads that do not have performance data recorded yet.
85 if (data.empty()) {
86 continue;
87 }
88 std::unique_ptr<Json::Value> dataJson = ReportPerformance::dumpToJson(data);
89 if (dataJson == nullptr) {
90 continue;
91 }
92 (*dataJson)["threadNum"] = item.first;
93 root.append(*dataJson);
94 }
95 Json::StyledWriter writer;
96 std::string rootStr = writer.write(root);
97 write(fd, rootStr.c_str(), rootStr.size());
98}
99
100void dumpPlots(int fd, const std::map<int, PerformanceData>& threadDataMap)
101{
102 if (fd < 0) {
103 return;
104 }
105
106 for (const auto &item : threadDataMap) {
107 const ReportPerformance::PerformanceData& data = item.second;
108 if (data.empty()) {
109 continue;
110 }
111 std::string hists = ReportPerformance::dumpHistogramsToString(data);
112 write(fd, hists.c_str(), hists.size());
113 }
114}
115
Eric Tanbc8281a2018-09-20 10:16:14 -0700116static std::string dumpRetroString(const PerformanceData& data, int64_t now)
117{
118 std::stringstream ss;
119 ss << NBLog::threadTypeToString(data.threadInfo.type) << "," << data.threadInfo.id << "\n";
120 for (const auto &item : data.snapshots) {
121 // TODO use an enum to string conversion method. One good idea:
122 // https://stackoverflow.com/a/238157
123 if (item.first == NBLog::EVENT_UNDERRUN) {
124 ss << "EVENT_UNDERRUN,";
125 } else if (item.first == NBLog::EVENT_OVERRUN) {
126 ss << "EVENT_OVERRUN,";
127 }
128 ss << now - item.second << "\n";
129 }
130 ss << "\n";
131 return ss.str();
132}
133
134void dumpRetro(int fd, const std::map<int, PerformanceData>& threadDataMap)
135{
136 if (fd < 0) {
137 return;
138 }
139
140 const nsecs_t now = systemTime();
141 for (const auto &item : threadDataMap) {
142 const ReportPerformance::PerformanceData& data = item.second;
143 if (data.snapshots.empty()) {
144 continue;
145 }
146 const std::string retroStr = dumpRetroString(data, now);
147 write(fd, retroStr.c_str(), retroStr.size());
148 }
149}
150
Eric Tand6eee712018-09-07 10:58:19 -0700151bool sendToMediaMetrics(const PerformanceData& data)
152{
153 // See documentation for these metrics here:
154 // docs.google.com/document/d/11--6dyOXVOpacYQLZiaOY5QVtQjUyqNx2zT9cCzLKYE/edit?usp=sharing
155 static constexpr char kThreadType[] = "android.media.audiothread.type";
156 static constexpr char kThreadFrameCount[] = "android.media.audiothread.framecount";
157 static constexpr char kThreadSampleRate[] = "android.media.audiothread.samplerate";
158 static constexpr char kThreadWorkHist[] = "android.media.audiothread.workMs.hist";
159 static constexpr char kThreadLatencyHist[] = "android.media.audiothread.latencyMs.hist";
160 static constexpr char kThreadWarmupHist[] = "android.media.audiothread.warmupMs.hist";
161 static constexpr char kThreadUnderruns[] = "android.media.audiothread.underruns";
162 static constexpr char kThreadOverruns[] = "android.media.audiothread.overruns";
163 static constexpr char kThreadActive[] = "android.media.audiothread.activeMs";
164 static constexpr char kThreadDuration[] = "android.media.audiothread.durationMs";
Eric Tanfefe3162018-09-07 10:09:11 -0700165
Eric Tan0513b5d2018-09-17 10:32:48 -0700166 // Currently, we only allow FastMixer thread data to be sent to Media Metrics.
167 if (data.threadInfo.type != NBLog::FASTMIXER) {
168 return false;
169 }
170
Ray Essickf27e9872019-12-07 06:28:46 -0800171 std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create("audiothread"));
Eric Tand6eee712018-09-07 10:58:19 -0700172
173 const Histogram &workHist = data.workHist;
174 if (workHist.totalCount() > 0) {
175 item->setCString(kThreadWorkHist, workHist.toString().c_str());
176 }
177
178 const Histogram &latencyHist = data.latencyHist;
179 if (latencyHist.totalCount() > 0) {
180 item->setCString(kThreadLatencyHist, latencyHist.toString().c_str());
181 }
182
183 const Histogram &warmupHist = data.warmupHist;
184 if (warmupHist.totalCount() > 0) {
185 item->setCString(kThreadWarmupHist, warmupHist.toString().c_str());
186 }
187
188 if (data.underruns > 0) {
189 item->setInt64(kThreadUnderruns, data.underruns);
190 }
191
192 if (data.overruns > 0) {
193 item->setInt64(kThreadOverruns, data.overruns);
194 }
195
196 // Send to Media Metrics if the record is not empty.
197 // The thread and time info are added inside the if statement because
198 // we want to send them only if there are performance metrics to send.
199 if (item->count() > 0) {
200 // Add thread info fields.
Eric Tanace588c2018-09-12 11:44:43 -0700201 const char * const typeString = NBLog::threadTypeToString(data.threadInfo.type);
202 item->setCString(kThreadType, typeString);
Eric Tan0513b5d2018-09-17 10:32:48 -0700203 item->setInt32(kThreadFrameCount, data.threadParams.frameCount);
204 item->setInt32(kThreadSampleRate, data.threadParams.sampleRate);
Eric Tand6eee712018-09-07 10:58:19 -0700205 // Add time info fields.
206 item->setInt64(kThreadActive, data.active / 1000000);
207 item->setInt64(kThreadDuration, (systemTime() - data.start) / 1000000);
208 return item->selfrecord();
209 }
210 return false;
211}
212
213//------------------------------------------------------------------------------
Sanna Catherine de Treville Wager847b6e62017-08-03 11:35:51 -0700214
215// TODO: use a function like this to extract logic from writeToFile
216// https://stackoverflow.com/a/9279620
217
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -0700218// Writes outlier intervals, timestamps, and histograms spanning long time intervals to file.
219// TODO: write data in binary format
Eric Tand6eee712018-09-07 10:58:19 -0700220void writeToFile(const std::deque<std::pair<timestamp, Hist>> &hists,
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700221 const std::deque<std::pair<msInterval, timestamp>> &outlierData,
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700222 const std::deque<timestamp> &peakTimestamps,
223 const char * directory, bool append, int author, log_hash_t hash) {
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -0700224
225 // TODO: remove old files, implement rotating files as in AudioFlinger.cpp
226
227 if (outlierData.empty() && hists.empty() && peakTimestamps.empty()) {
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700228 ALOGW("No data, returning.");
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700229 return;
230 }
231
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700232 std::stringstream outlierName;
233 std::stringstream histogramName;
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700234 std::stringstream peakName;
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700235
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -0700236 // get current time
237 char currTime[16]; //YYYYMMDDHHMMSS + '\0' + one unused
238 struct timeval tv;
239 gettimeofday(&tv, NULL);
240 struct tm tm;
241 localtime_r(&tv.tv_sec, &tm);
242 strftime(currTime, sizeof(currTime), "%Y%m%d%H%M%S", &tm);
243
244 // generate file names
245 std::stringstream common;
246 common << author << "_" << hash << "_" << currTime << ".csv";
247
248 histogramName << directory << "histograms_" << common.str();
249 outlierName << directory << "outliers_" << common.str();
250 peakName << directory << "peaks_" << common.str();
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700251
252 std::ofstream hfs;
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700253 hfs.open(histogramName.str(), append ? std::ios::app : std::ios::trunc);
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700254 if (!hfs.is_open()) {
255 ALOGW("couldn't open file %s", histogramName.str().c_str());
256 return;
257 }
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -0700258 // each histogram is written as a line where the first value is the timestamp and
259 // subsequent values are pairs of buckets and counts. Each value is separated
260 // by a comma, and each histogram is separated by a newline.
Sanna Catherine de Treville Wager847b6e62017-08-03 11:35:51 -0700261 for (auto hist = hists.begin(); hist != hists.end(); ++hist) {
262 hfs << hist->first << ", ";
263 for (auto bucket = hist->second.begin(); bucket != hist->second.end(); ++bucket) {
264 hfs << bucket->first / static_cast<double>(kJiffyPerMs)
265 << ", " << bucket->second;
266 if (std::next(bucket) != end(hist->second)) {
267 hfs << ", ";
268 }
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700269 }
Sanna Catherine de Treville Wager847b6e62017-08-03 11:35:51 -0700270 if (std::next(hist) != end(hists)) {
271 hfs << "\n";
272 }
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700273 }
274 hfs.close();
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700275
276 std::ofstream ofs;
277 ofs.open(outlierName.str(), append ? std::ios::app : std::ios::trunc);
278 if (!ofs.is_open()) {
279 ALOGW("couldn't open file %s", outlierName.str().c_str());
280 return;
281 }
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -0700282 // outliers are written as pairs separated by newlines, where each
283 // pair's values are separated by a comma
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700284 for (const auto &outlier : outlierData) {
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -0700285 ofs << outlier.first << ", " << outlier.second << "\n";
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700286 }
287 ofs.close();
288
289 std::ofstream pfs;
290 pfs.open(peakName.str(), append ? std::ios::app : std::ios::trunc);
291 if (!pfs.is_open()) {
292 ALOGW("couldn't open file %s", peakName.str().c_str());
293 return;
294 }
Sanna Catherine de Treville Wager2a6a9452017-07-28 11:02:01 -0700295 // peaks are simply timestamps separated by commas
Sanna Catherine de Treville Wager847b6e62017-08-03 11:35:51 -0700296 for (auto peak = peakTimestamps.begin(); peak != peakTimestamps.end(); ++peak) {
297 pfs << *peak;
298 if (std::next(peak) != end(peakTimestamps)) {
299 pfs << ", ";
300 }
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700301 }
302 pfs.close();
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700303}
304
Eric Tanace588c2018-09-12 11:44:43 -0700305} // namespace ReportPerformance
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700306} // namespace android