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 DEBUG false // STOPSHIP if true 18 #include "Log.h" 19 #include "statslog.h" 20 21 #include <android-base/file.h> 22 #include <dirent.h> 23 #include "StatsLogProcessor.h" 24 #include "stats_log_util.h" 25 #include "android-base/stringprintf.h" 26 #include "guardrail/StatsdStats.h" 27 #include "metrics/CountMetricProducer.h" 28 #include "external/StatsPullerManager.h" 29 #include "stats_util.h" 30 #include "storage/StorageManager.h" 31 32 #include <log/log_event_list.h> 33 #include <utils/Errors.h> 34 #include <utils/SystemClock.h> 35 36 using namespace android; 37 using android::base::StringPrintf; 38 using android::util::FIELD_COUNT_REPEATED; 39 using android::util::FIELD_TYPE_BOOL; 40 using android::util::FIELD_TYPE_FLOAT; 41 using android::util::FIELD_TYPE_INT32; 42 using android::util::FIELD_TYPE_INT64; 43 using android::util::FIELD_TYPE_MESSAGE; 44 using android::util::FIELD_TYPE_STRING; 45 using android::util::ProtoOutputStream; 46 using std::make_unique; 47 using std::unique_ptr; 48 using std::vector; 49 50 namespace android { 51 namespace os { 52 namespace statsd { 53 54 // for ConfigMetricsReportList 55 const int FIELD_ID_CONFIG_KEY = 1; 56 const int FIELD_ID_REPORTS = 2; 57 // for ConfigKey 58 const int FIELD_ID_UID = 1; 59 const int FIELD_ID_ID = 2; 60 // for ConfigMetricsReport 61 // const int FIELD_ID_METRICS = 1; // written in MetricsManager.cpp 62 const int FIELD_ID_UID_MAP = 2; 63 const int FIELD_ID_LAST_REPORT_ELAPSED_NANOS = 3; 64 const int FIELD_ID_CURRENT_REPORT_ELAPSED_NANOS = 4; 65 const int FIELD_ID_LAST_REPORT_WALL_CLOCK_NANOS = 5; 66 const int FIELD_ID_CURRENT_REPORT_WALL_CLOCK_NANOS = 6; 67 const int FIELD_ID_DUMP_REPORT_REASON = 8; 68 const int FIELD_ID_STRINGS = 9; 69 70 #define NS_PER_HOUR 3600 * NS_PER_SEC 71 72 #define STATS_DATA_DIR "/data/misc/stats-data" 73 74 StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, 75 const sp<AlarmMonitor>& anomalyAlarmMonitor, 76 const sp<AlarmMonitor>& periodicAlarmMonitor, 77 const int64_t timeBaseNs, 78 const std::function<bool(const ConfigKey&)>& sendBroadcast) 79 : mUidMap(uidMap), 80 mAnomalyAlarmMonitor(anomalyAlarmMonitor), 81 mPeriodicAlarmMonitor(periodicAlarmMonitor), 82 mSendBroadcast(sendBroadcast), 83 mTimeBaseNs(timeBaseNs), 84 mLargestTimestampSeen(0), 85 mLastTimestampSeen(0) { 86 mStatsPullerManager.ForceClearPullerCache(); 87 } 88 89 StatsLogProcessor::~StatsLogProcessor() { 90 } 91 92 void StatsLogProcessor::onAnomalyAlarmFired( 93 const int64_t& timestampNs, 94 unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet) { 95 std::lock_guard<std::mutex> lock(mMetricsMutex); 96 for (const auto& itr : mMetricsManagers) { 97 itr.second->onAnomalyAlarmFired(timestampNs, alarmSet); 98 } 99 } 100 void StatsLogProcessor::onPeriodicAlarmFired( 101 const int64_t& timestampNs, 102 unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet) { 103 104 std::lock_guard<std::mutex> lock(mMetricsMutex); 105 for (const auto& itr : mMetricsManagers) { 106 itr.second->onPeriodicAlarmFired(timestampNs, alarmSet); 107 } 108 } 109 110 void updateUid(Value* value, int hostUid) { 111 int uid = value->int_value; 112 if (uid != hostUid) { 113 value->setInt(hostUid); 114 } 115 } 116 117 void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { 118 if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(event->GetTagId()) != 119 android::util::AtomsInfo::kAtomsWithAttributionChain.end()) { 120 for (auto& value : *(event->getMutableValues())) { 121 if (value.mField.getPosAtDepth(0) > kAttributionField) { 122 break; 123 } 124 if (isAttributionUidField(value)) { 125 const int hostUid = mUidMap->getHostUidOrSelf(value.mValue.int_value); 126 updateUid(&value.mValue, hostUid); 127 } 128 } 129 } else { 130 auto it = android::util::AtomsInfo::kAtomsWithUidField.find(event->GetTagId()); 131 if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { 132 int uidField = it->second; // uidField is the field number in proto, 133 // starting from 1 134 if (uidField > 0 && (int)event->getValues().size() >= uidField && 135 (event->getValues())[uidField - 1].mValue.getType() == INT) { 136 Value& value = (*event->getMutableValues())[uidField - 1].mValue; 137 const int hostUid = mUidMap->getHostUidOrSelf(value.int_value); 138 updateUid(&value, hostUid); 139 } else { 140 ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); 141 } 142 } 143 } 144 } 145 146 void StatsLogProcessor::onIsolatedUidChangedEventLocked(const LogEvent& event) { 147 status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR; 148 bool is_create = event.GetBool(3, &err); 149 auto parent_uid = int(event.GetLong(1, &err2)); 150 auto isolated_uid = int(event.GetLong(2, &err3)); 151 if (err == NO_ERROR && err2 == NO_ERROR && err3 == NO_ERROR) { 152 if (is_create) { 153 mUidMap->assignIsolatedUid(isolated_uid, parent_uid); 154 } else { 155 mUidMap->removeIsolatedUid(isolated_uid, parent_uid); 156 } 157 } else { 158 ALOGE("Failed to parse uid in the isolated uid change event."); 159 } 160 } 161 162 void StatsLogProcessor::OnLogEvent(LogEvent* event) { 163 OnLogEvent(event, false); 164 } 165 166 void StatsLogProcessor::resetConfigs() { 167 std::lock_guard<std::mutex> lock(mMetricsMutex); 168 resetConfigsLocked(getElapsedRealtimeNs()); 169 } 170 171 void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs) { 172 std::vector<ConfigKey> configKeys; 173 for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) { 174 configKeys.push_back(it->first); 175 } 176 resetConfigsLocked(timestampNs, configKeys); 177 } 178 179 void StatsLogProcessor::OnLogEvent(LogEvent* event, bool reconnected) { 180 std::lock_guard<std::mutex> lock(mMetricsMutex); 181 182 #ifdef VERY_VERBOSE_PRINTING 183 if (mPrintAllLogs) { 184 ALOGI("%s", event->ToString().c_str()); 185 } 186 #endif 187 const int64_t currentTimestampNs = event->GetElapsedTimestampNs(); 188 189 if (reconnected && mLastTimestampSeen != 0) { 190 // LogReader tells us the connection has just been reset. Now we need 191 // to enter reconnection state to find the last CP. 192 mInReconnection = true; 193 } 194 195 if (mInReconnection) { 196 // We see the checkpoint 197 if (currentTimestampNs == mLastTimestampSeen) { 198 mInReconnection = false; 199 // Found the CP. ignore this event, and we will start to read from next event. 200 return; 201 } 202 if (currentTimestampNs > mLargestTimestampSeen) { 203 // We see a new log but CP has not been found yet. Give up now. 204 mLogLossCount++; 205 mInReconnection = false; 206 StatsdStats::getInstance().noteLogLost(currentTimestampNs); 207 // Persist the data before we reset. Do we want this? 208 WriteDataToDiskLocked(CONFIG_RESET); 209 // We see fresher event before we see the checkpoint. We might have lost data. 210 // The best we can do is to reset. 211 resetConfigsLocked(currentTimestampNs); 212 } else { 213 // Still in search of the CP. Keep going. 214 return; 215 } 216 } 217 218 mLogCount++; 219 mLastTimestampSeen = currentTimestampNs; 220 if (mLargestTimestampSeen < currentTimestampNs) { 221 mLargestTimestampSeen = currentTimestampNs; 222 } 223 224 resetIfConfigTtlExpiredLocked(currentTimestampNs); 225 226 StatsdStats::getInstance().noteAtomLogged( 227 event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC); 228 229 // Hard-coded logic to update the isolated uid's in the uid-map. 230 // The field numbers need to be currently updated by hand with atoms.proto 231 if (event->GetTagId() == android::util::ISOLATED_UID_CHANGED) { 232 onIsolatedUidChangedEventLocked(*event); 233 } 234 235 if (mMetricsManagers.empty()) { 236 return; 237 } 238 239 int64_t curTimeSec = getElapsedRealtimeSec(); 240 if (curTimeSec - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) { 241 mStatsPullerManager.ClearPullerCacheIfNecessary(curTimeSec * NS_PER_SEC); 242 mLastPullerCacheClearTimeSec = curTimeSec; 243 } 244 245 246 if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) { 247 // Map the isolated uid to host uid if necessary. 248 mapIsolatedUidToHostUidIfNecessaryLocked(event); 249 } 250 251 // pass the event to metrics managers. 252 for (auto& pair : mMetricsManagers) { 253 pair.second->onLogEvent(*event); 254 flushIfNecessaryLocked(event->GetElapsedTimestampNs(), pair.first, *(pair.second)); 255 } 256 } 257 258 void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, 259 const StatsdConfig& config) { 260 std::lock_guard<std::mutex> lock(mMetricsMutex); 261 WriteDataToDiskLocked(key, timestampNs, CONFIG_UPDATED); 262 OnConfigUpdatedLocked(timestampNs, key, config); 263 } 264 265 void StatsLogProcessor::OnConfigUpdatedLocked( 266 const int64_t timestampNs, const ConfigKey& key, const StatsdConfig& config) { 267 VLOG("Updated configuration for key %s", key.ToString().c_str()); 268 sp<MetricsManager> newMetricsManager = 269 new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap, 270 mAnomalyAlarmMonitor, mPeriodicAlarmMonitor); 271 if (newMetricsManager->isConfigValid()) { 272 mUidMap->OnConfigUpdated(key); 273 if (newMetricsManager->shouldAddUidMapListener()) { 274 // We have to add listener after the MetricsManager is constructed because it's 275 // not safe to create wp or sp from this pointer inside its constructor. 276 mUidMap->addListener(newMetricsManager.get()); 277 } 278 newMetricsManager->refreshTtl(timestampNs); 279 mMetricsManagers[key] = newMetricsManager; 280 VLOG("StatsdConfig valid"); 281 } else { 282 // If there is any error in the config, don't use it. 283 ALOGE("StatsdConfig NOT valid"); 284 } 285 } 286 287 size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const { 288 std::lock_guard<std::mutex> lock(mMetricsMutex); 289 auto it = mMetricsManagers.find(key); 290 if (it == mMetricsManagers.end()) { 291 ALOGW("Config source %s does not exist", key.ToString().c_str()); 292 return 0; 293 } 294 return it->second->byteSize(); 295 } 296 297 void StatsLogProcessor::dumpStates(FILE* out, bool verbose) { 298 std::lock_guard<std::mutex> lock(mMetricsMutex); 299 fprintf(out, "MetricsManager count: %lu\n", (unsigned long)mMetricsManagers.size()); 300 for (auto metricsManager : mMetricsManagers) { 301 metricsManager.second->dumpStates(out, verbose); 302 } 303 } 304 305 /* 306 * onDumpReport dumps serialized ConfigMetricsReportList into outData. 307 */ 308 void StatsLogProcessor::onDumpReport(const ConfigKey& key, const int64_t dumpTimeStampNs, 309 const bool include_current_partial_bucket, 310 const DumpReportReason dumpReportReason, 311 vector<uint8_t>* outData) { 312 std::lock_guard<std::mutex> lock(mMetricsMutex); 313 314 ProtoOutputStream proto; 315 316 // Start of ConfigKey. 317 uint64_t configKeyToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY); 318 proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid()); 319 proto.write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)key.GetId()); 320 proto.end(configKeyToken); 321 // End of ConfigKey. 322 323 // Then, check stats-data directory to see there's any file containing 324 // ConfigMetricsReport from previous shutdowns to concatenate to reports. 325 StorageManager::appendConfigMetricsReport(key, &proto); 326 327 auto it = mMetricsManagers.find(key); 328 if (it != mMetricsManagers.end()) { 329 // This allows another broadcast to be sent within the rate-limit period if we get close to 330 // filling the buffer again soon. 331 mLastBroadcastTimes.erase(key); 332 333 // Start of ConfigMetricsReport (reports). 334 uint64_t reportsToken = 335 proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS); 336 onConfigMetricsReportLocked(key, dumpTimeStampNs, include_current_partial_bucket, 337 dumpReportReason, &proto); 338 proto.end(reportsToken); 339 // End of ConfigMetricsReport (reports). 340 } else { 341 ALOGW("Config source %s does not exist", key.ToString().c_str()); 342 } 343 344 if (outData != nullptr) { 345 outData->clear(); 346 outData->resize(proto.size()); 347 size_t pos = 0; 348 auto iter = proto.data(); 349 while (iter.readBuffer() != NULL) { 350 size_t toRead = iter.currentToRead(); 351 std::memcpy(&((*outData)[pos]), iter.readBuffer(), toRead); 352 pos += toRead; 353 iter.rp()->move(toRead); 354 } 355 } 356 357 StatsdStats::getInstance().noteMetricsReportSent(key, proto.size()); 358 } 359 360 /* 361 * onConfigMetricsReportLocked dumps serialized ConfigMetricsReport into outData. 362 */ 363 void StatsLogProcessor::onConfigMetricsReportLocked(const ConfigKey& key, 364 const int64_t dumpTimeStampNs, 365 const bool include_current_partial_bucket, 366 const DumpReportReason dumpReportReason, 367 ProtoOutputStream* proto) { 368 // We already checked whether key exists in mMetricsManagers in 369 // WriteDataToDisk. 370 auto it = mMetricsManagers.find(key); 371 if (it == mMetricsManagers.end()) { 372 return; 373 } 374 int64_t lastReportTimeNs = it->second->getLastReportTimeNs(); 375 int64_t lastReportWallClockNs = it->second->getLastReportWallClockNs(); 376 377 std::set<string> str_set; 378 379 // First, fill in ConfigMetricsReport using current data on memory, which 380 // starts from filling in StatsLogReport's. 381 it->second->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, 382 &str_set, proto); 383 384 // Fill in UidMap if there is at least one metric to report. 385 // This skips the uid map if it's an empty config. 386 if (it->second->getNumMetrics() > 0) { 387 uint64_t uidMapToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP); 388 if (it->second->hashStringInReport()) { 389 mUidMap->appendUidMap(dumpTimeStampNs, key, &str_set, proto); 390 } else { 391 mUidMap->appendUidMap(dumpTimeStampNs, key, nullptr, proto); 392 } 393 proto->end(uidMapToken); 394 } 395 396 // Fill in the timestamps. 397 proto->write(FIELD_TYPE_INT64 | FIELD_ID_LAST_REPORT_ELAPSED_NANOS, 398 (long long)lastReportTimeNs); 399 proto->write(FIELD_TYPE_INT64 | FIELD_ID_CURRENT_REPORT_ELAPSED_NANOS, 400 (long long)dumpTimeStampNs); 401 proto->write(FIELD_TYPE_INT64 | FIELD_ID_LAST_REPORT_WALL_CLOCK_NANOS, 402 (long long)lastReportWallClockNs); 403 proto->write(FIELD_TYPE_INT64 | FIELD_ID_CURRENT_REPORT_WALL_CLOCK_NANOS, 404 (long long)getWallClockNs()); 405 // Dump report reason 406 proto->write(FIELD_TYPE_INT32 | FIELD_ID_DUMP_REPORT_REASON, dumpReportReason); 407 408 for (const auto& str : str_set) { 409 proto->write(FIELD_TYPE_STRING | FIELD_COUNT_REPEATED | FIELD_ID_STRINGS, str); 410 } 411 } 412 413 void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs, 414 const std::vector<ConfigKey>& configs) { 415 for (const auto& key : configs) { 416 StatsdConfig config; 417 if (StorageManager::readConfigFromDisk(key, &config)) { 418 OnConfigUpdatedLocked(timestampNs, key, config); 419 StatsdStats::getInstance().noteConfigReset(key); 420 } else { 421 ALOGE("Failed to read backup config from disk for : %s", key.ToString().c_str()); 422 auto it = mMetricsManagers.find(key); 423 if (it != mMetricsManagers.end()) { 424 it->second->refreshTtl(timestampNs); 425 } 426 } 427 } 428 } 429 430 void StatsLogProcessor::resetIfConfigTtlExpiredLocked(const int64_t timestampNs) { 431 std::vector<ConfigKey> configKeysTtlExpired; 432 for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) { 433 if (it->second != nullptr && !it->second->isInTtl(timestampNs)) { 434 configKeysTtlExpired.push_back(it->first); 435 } 436 } 437 if (configKeysTtlExpired.size() > 0) { 438 WriteDataToDiskLocked(CONFIG_RESET); 439 resetConfigsLocked(timestampNs, configKeysTtlExpired); 440 } 441 } 442 443 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { 444 std::lock_guard<std::mutex> lock(mMetricsMutex); 445 auto it = mMetricsManagers.find(key); 446 if (it != mMetricsManagers.end()) { 447 WriteDataToDiskLocked(key, getElapsedRealtimeNs(), CONFIG_REMOVED); 448 mMetricsManagers.erase(it); 449 mUidMap->OnConfigRemoved(key); 450 } 451 StatsdStats::getInstance().noteConfigRemoved(key); 452 453 mLastBroadcastTimes.erase(key); 454 455 if (mMetricsManagers.empty()) { 456 mStatsPullerManager.ForceClearPullerCache(); 457 } 458 } 459 460 void StatsLogProcessor::flushIfNecessaryLocked( 461 int64_t timestampNs, const ConfigKey& key, MetricsManager& metricsManager) { 462 auto lastCheckTime = mLastByteSizeTimes.find(key); 463 if (lastCheckTime != mLastByteSizeTimes.end()) { 464 if (timestampNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) { 465 return; 466 } 467 } 468 469 // We suspect that the byteSize() computation is expensive, so we set a rate limit. 470 size_t totalBytes = metricsManager.byteSize(); 471 mLastByteSizeTimes[key] = timestampNs; 472 bool requestDump = false; 473 if (totalBytes > 474 StatsdStats::kMaxMetricsBytesPerConfig) { // Too late. We need to start clearing data. 475 metricsManager.dropData(timestampNs); 476 StatsdStats::getInstance().noteDataDropped(key); 477 VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str()); 478 } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) || 479 (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) { 480 // Request to send a broadcast if: 481 // 1. in memory data > threshold OR 482 // 2. config has old data report on disk. 483 requestDump = true; 484 } 485 486 if (requestDump) { 487 // Send broadcast so that receivers can pull data. 488 auto lastBroadcastTime = mLastBroadcastTimes.find(key); 489 if (lastBroadcastTime != mLastBroadcastTimes.end()) { 490 if (timestampNs - lastBroadcastTime->second < StatsdStats::kMinBroadcastPeriodNs) { 491 VLOG("StatsD would've sent a broadcast but the rate limit stopped us."); 492 return; 493 } 494 } 495 if (mSendBroadcast(key)) { 496 mOnDiskDataConfigs.erase(key); 497 VLOG("StatsD triggered data fetch for %s", key.ToString().c_str()); 498 mLastBroadcastTimes[key] = timestampNs; 499 StatsdStats::getInstance().noteBroadcastSent(key); 500 } 501 } 502 } 503 504 void StatsLogProcessor::WriteDataToDiskLocked(const ConfigKey& key, 505 const int64_t timestampNs, 506 const DumpReportReason dumpReportReason) { 507 if (mMetricsManagers.find(key) == mMetricsManagers.end() || 508 !mMetricsManagers.find(key)->second->shouldWriteToDisk()) { 509 return; 510 } 511 ProtoOutputStream proto; 512 onConfigMetricsReportLocked(key, timestampNs, true /* include_current_partial_bucket*/, 513 dumpReportReason, &proto); 514 string file_name = StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, 515 (long)getWallClockSec(), key.GetUid(), (long long)key.GetId()); 516 android::base::unique_fd fd(open(file_name.c_str(), 517 O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR)); 518 if (fd == -1) { 519 ALOGE("Attempt to write %s but failed", file_name.c_str()); 520 return; 521 } 522 proto.flush(fd.get()); 523 // We were able to write the ConfigMetricsReport to disk, so we should trigger collection ASAP. 524 mOnDiskDataConfigs.insert(key); 525 } 526 527 void StatsLogProcessor::WriteDataToDiskLocked(const DumpReportReason dumpReportReason) { 528 const int64_t timeNs = getElapsedRealtimeNs(); 529 for (auto& pair : mMetricsManagers) { 530 WriteDataToDiskLocked(pair.first, timeNs, dumpReportReason); 531 } 532 } 533 534 void StatsLogProcessor::WriteDataToDisk(const DumpReportReason dumpReportReason) { 535 std::lock_guard<std::mutex> lock(mMetricsMutex); 536 WriteDataToDiskLocked(dumpReportReason); 537 } 538 539 void StatsLogProcessor::informPullAlarmFired(const int64_t timestampNs) { 540 std::lock_guard<std::mutex> lock(mMetricsMutex); 541 mStatsPullerManager.OnAlarmFired(timestampNs); 542 } 543 544 int64_t StatsLogProcessor::getLastReportTimeNs(const ConfigKey& key) { 545 auto it = mMetricsManagers.find(key); 546 if (it == mMetricsManagers.end()) { 547 return 0; 548 } else { 549 return it->second->getLastReportTimeNs(); 550 } 551 } 552 553 void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) { 554 std::lock_guard<std::mutex> lock(mMetricsMutex); 555 mOnDiskDataConfigs.insert(key); 556 } 557 558 } // namespace statsd 559 } // namespace os 560 } // namespace android 561