1 /* 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" 18 //#define LOG_NDEBUG 0 19 20 #include <fstream> 21 #include <iostream> 22 #include <memory> 23 #include <queue> 24 #include <stdarg.h> 25 #include <stdint.h> 26 #include <stdio.h> 27 #include <string.h> 28 #include <sstream> 29 #include <sys/prctl.h> 30 #include <sys/time.h> 31 #include <utility> 32 #include <json/json.h> 33 #include <media/MediaAnalyticsItem.h> 34 #include <media/nblog/Events.h> 35 #include <media/nblog/PerformanceAnalysis.h> 36 #include <media/nblog/ReportPerformance.h> 37 #include <utils/Log.h> 38 #include <utils/String8.h> 39 40 namespace android { 41 namespace ReportPerformance { 42 43 static std::unique_ptr<Json::Value> dumpToJson(const PerformanceData& data) 44 { 45 std::unique_ptr<Json::Value> rootPtr = std::make_unique<Json::Value>(Json::objectValue); 46 Json::Value& root = *rootPtr; 47 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; 51 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 } 60 61 static 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 75 void 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 100 void 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 116 static 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 134 void 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 151 bool 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"; 165 166 // 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 171 std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create("audiothread")); 172 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. 201 const char * const typeString = NBLog::threadTypeToString(data.threadInfo.type); 202 item->setCString(kThreadType, typeString); 203 item->setInt32(kThreadFrameCount, data.threadParams.frameCount); 204 item->setInt32(kThreadSampleRate, data.threadParams.sampleRate); 205 // 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 //------------------------------------------------------------------------------ 214 215 // TODO: use a function like this to extract logic from writeToFile 216 // https://stackoverflow.com/a/9279620 217 218 // Writes outlier intervals, timestamps, and histograms spanning long time intervals to file. 219 // TODO: write data in binary format 220 void writeToFile(const std::deque<std::pair<timestamp, Hist>> &hists, 221 const std::deque<std::pair<msInterval, timestamp>> &outlierData, 222 const std::deque<timestamp> &peakTimestamps, 223 const char * directory, bool append, int author, log_hash_t hash) { 224 225 // TODO: remove old files, implement rotating files as in AudioFlinger.cpp 226 227 if (outlierData.empty() && hists.empty() && peakTimestamps.empty()) { 228 ALOGW("No data, returning."); 229 return; 230 } 231 232 std::stringstream outlierName; 233 std::stringstream histogramName; 234 std::stringstream peakName; 235 236 // 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(); 251 252 std::ofstream hfs; 253 hfs.open(histogramName.str(), append ? std::ios::app : std::ios::trunc); 254 if (!hfs.is_open()) { 255 ALOGW("couldn't open file %s", histogramName.str().c_str()); 256 return; 257 } 258 // 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. 261 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 } 269 } 270 if (std::next(hist) != end(hists)) { 271 hfs << "\n"; 272 } 273 } 274 hfs.close(); 275 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 } 282 // outliers are written as pairs separated by newlines, where each 283 // pair's values are separated by a comma 284 for (const auto &outlier : outlierData) { 285 ofs << outlier.first << ", " << outlier.second << "\n"; 286 } 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 } 295 // peaks are simply timestamps separated by commas 296 for (auto peak = peakTimestamps.begin(); peak != peakTimestamps.end(); ++peak) { 297 pfs << *peak; 298 if (std::next(peak) != end(peakTimestamps)) { 299 pfs << ", "; 300 } 301 } 302 pfs.close(); 303 } 304 305 } // namespace ReportPerformance 306 } // namespace android 307