之前我们分析过关于Android log机制,在这里我们再详细说下,log丢失的原理。

一、统计log

logd监听了logdw的socket来保存从log打印函数通过logdw socket传过来的log,最后会调用LogBuffer::log函数,在log函数最后会调用如下两个函数。

[cpp] view plaincopy
  1. stats.add(elem);
  2. maybePrune(log_id);

这里的log_id就是radio,main,event等。

我们先来看LogStatistics::add函数

[cpp] view plaincopy
  1. void LogStatistics::add(LogBufferElement *e) {
  2. log_id_t log_id = e->getLogId();
  3. unsigned short size = e->getMsgLen();//获取消息长度
  4. mSizes[log_id] += size;//对每个log_id做长度累计
  5. ++mElements[log_id];//对每个log_id做消息个数累计
  6. mSizesTotal[log_id] += size;
  7. ++mElementsTotal[log_id];
  8. if (log_id == LOG_ID_KERNEL) {
  9. return;
  10. }
  11. uidTable[log_id].add(e->getUid(), e);
  12. if (!enable) {
  13. return;
  14. }
  15. pidTable.add(e->getPid(), e);
  16. tidTable.add(e->getTid(), e);
  17. uint32_t tag = e->getTag();
  18. if (tag) {
  19. tagTable.add(tag, e);
  20. }
  21. }

这个函数,对每个log_id的消息长度做统计,消息数量也做了统计。

二、删除log判定

我们再来看下这个maybePrune函数

[cpp] view plaincopy
  1. // Prune at most 10% of the log entries or 256, whichever is less.
  2. //
  3. // mLogElementsLock must be held when this function is called.
  4. void LogBuffer::maybePrune(log_id_t id) {
  5. size_t sizes = stats.sizes(id);//每个log_id统计的所有消息长度
  6. unsigned long maxSize = log_buffer_size(id);//每个log_id缓存的最大值,之前在init函数里面初始化的
  7. if (sizes > maxSize) {
  8. size_t sizeOver = sizes - ((maxSize * 9) / 10);//超过90%size的那部分
  9. size_t elements = stats.elements(id);
  10. size_t minElements = elements / 10;//10%的elements
  11. unsigned long pruneRows = elements * sizeOver / sizes;//超过90%size的elements
  12. if (pruneRows <= minElements) {
  13. pruneRows = minElements;
  14. }
  15. if (pruneRows > 256) {
  16. pruneRows = 256;
  17. }
  18. prune(id, pruneRows);
  19. }
  20. }

在之前的博客中我们分析过了,每个log_id的size是如何而来的。可以通过属性获取。

这里保存elements的是mLogElements,只是保存的LogBufferElement 的指针而已,实际不会占用多大的内存。

[cpp] view plaincopy
  1. typedef std::list<LogBufferElement *> LogBufferElementCollection;
  2. class LogBuffer {
  3. LogBufferElementCollection mLogElements;

而且只有每个element被调用erase,才会被真正销毁内存。

[cpp] view plaincopy
  1. LogBufferElementCollection::iterator LogBuffer::erase(
  2. LogBufferElementCollection::iterator it, bool engageStats) {
  3. LogBufferElement *e = *it;
  4. log_id_t id = e->getLogId();
  5. LogBufferIteratorMap::iterator f = mLastWorstUid[id].find(e->getUid());
  6. if ((f != mLastWorstUid[id].end()) && (it == f->second)) {
  7. mLastWorstUid[id].erase(f);
  8. }
  9. it = mLogElements.erase(it);
  10. if (engageStats) {
  11. stats.subtract(e);
  12. } else {
  13. stats.erase(e);
  14. }
  15. delete e;//销毁内存
  16. return it;
  17. }

所以每个log_id设定的值,不是一个缓存,而是一个警戒值。超过这个值,就要对特定log删除。

三、prune函数

prune函数主要就是删除log,在删除log的时候也做了白名单和黑名单的机制。

3.1 白名单 & 黑名单

这里我们先来看看LogBuffer的initPrune函数,这是用来设定白名单和黑名单的。

[cpp] view plaincopy
  1. int initPrune(char *cp) { return mPrune.init(cp); }

至于init这个函数我们就不看了,主要是解析字符串,把uid,pid保存下来。

那么又在哪里设定白名单和黑名单呢?我们可以通过如下命令,最后就调用了initPrune函数来设定白黑名单了。

[cpp] view plaincopy
  1. int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli,
  2. int argc, char **argv) {
  3. setname();
  4. if (!clientHasLogCredentials(cli)) {
  5. cli->sendMsg("Permission Denied");
  6. return 0;
  7. }
  8. char *cp = NULL;
  9. for (int i = 1; i < argc; ++i) {
  10. char *p = cp;
  11. if (p) {
  12. cp = NULL;
  13. asprintf(&cp, "%s %s", p, argv[i]);
  14. free(p);
  15. } else {
  16. asprintf(&cp, "%s", argv[i]);
  17. }
  18. }
  19. int ret = mBuf.initPrune(cp);
  20. free(cp);
  21. if (ret) {
  22. cli->sendMsg("Invalid");
  23. return 0;
  24. }
  25. cli->sendMsg("success");
  26. return 0;
  27. }

而每个白名单和黑名单的匹配就是看uid和pid的。这块就不细看了。

3.2 黑名单处理 & log最多的uid处理

下面我们就来看下prune这个函数的黑名单部分处理:

[cpp] view plaincopy
  1. void LogBuffer::prune(log_id_t id, unsigned long pruneRows, uid_t caller_uid) {
  2. ......
  3. // prune by worst offender by uid
  4. bool hasBlacklist = mPrune.naughty();//这块是黑名单部分
  5. while (pruneRows > 0) {
  6. // recalculate the worst offender on every batched pass
  7. uid_t worst = (uid_t) -1;
  8. size_t worst_sizes = 0;
  9. size_t second_worst_sizes = 0;
  10. if (worstUidEnabledForLogid(id) && mPrune.worstUidEnabled()) {
  11. std::unique_ptr<const UidEntry *[]> sorted = stats.sort(2, id);//得到log最多的2个uid
  12. if (sorted.get()) {
  13. if (sorted[0] && sorted[1]) {
  14. worst_sizes = sorted[0]->getSizes();
  15. // Calculate threshold as 12.5% of available storage
  16. size_t threshold = log_buffer_size(id) / 8;
  17. if ((worst_sizes > threshold)//大于阈值
  18. // Allow time horizon to extend roughly tenfold, assume
  19. // average entry length is 100 characters.
  20. && (worst_sizes > (10 * sorted[0]->getDropped()))) {
  21. worst = sorted[0]->getKey();//最多lod uid的size
  22. second_worst_sizes = sorted[1]->getSizes();
  23. if (second_worst_sizes < threshold) {
  24. second_worst_sizes = threshold;
  25. }
  26. }
  27. }
  28. }
  29. }
  30. // skip if we have neither worst nor naughty filters
  31. if ((worst == (uid_t) -1) && !hasBlacklist) {//如果没有这样的uid,也没有黑名单,直接跳过
  32. break;
  33. }
  34. bool kick = false;
  35. bool leading = true;
  36. it = mLogElements.begin();
  37. // Perform at least one mandatory garbage collection cycle in following
  38. // - clear leading chatty tags
  39. // - merge chatty tags
  40. // - check age-out of preserved logs
  41. bool gc = pruneRows <= 1;
  42. if (!gc && (worst != (uid_t) -1)) {
  43. LogBufferIteratorMap::iterator f = mLastWorstUid[id].find(worst);//查找这uid
  44. if ((f != mLastWorstUid[id].end())
  45. && (f->second != mLogElements.end())) {
  46. leading = false;//找到了,leading为false
  47. it = f->second;
  48. }
  49. }
  50. static const timespec too_old = {
  51. EXPIRE_HOUR_THRESHOLD * 60 * 60, 0
  52. };
  53. LogBufferElementCollection::iterator lastt;
  54. lastt = mLogElements.end();
  55. --lastt;
  56. LogBufferElementLast last;
  57. while (it != mLogElements.end()) {
  58. LogBufferElement *e = *it;
  59. if (oldest && (oldest->mStart <= e->getSequence())) {
  60. break;
  61. }
  62. if (e->getLogId() != id) {//log_id不对 continue
  63. ++it;
  64. continue;
  65. }
  66. unsigned short dropped = e->getDropped();//是否被free过mMsg
  67. // remove any leading drops
  68. if (leading && dropped) {
  69. it = erase(it);
  70. continue;
  71. }
  72. // merge any drops
  73. if (dropped && last.merge(e, dropped)) {// 合并之前删除的element
  74. it = erase(it, false);
  75. continue;
  76. }
  77. if (hasBlacklist && mPrune.naughty(e)) {//如果满足黑名单,删除这条element
  78. last.clear(e);
  79. it = erase(it);
  80. if (dropped) {//如果当前是dropoed,直接continue
  81. continue;
  82. }
  83. pruneRows--;//删除log的计数
  84. if (pruneRows == 0) {
  85. break;
  86. }
  87. if (e->getUid() == worst) {
  88. kick = true;
  89. if (worst_sizes < second_worst_sizes) {//最差值,小于第二个直接跳过
  90. break;
  91. }
  92. worst_sizes -= e->getMsgLen();//最差的值累减
  93. }
  94. continue;
  95. }
  96. if ((e->getRealTime() < ((*lastt)->getRealTime() - too_old))
  97. || (e->getRealTime() > (*lastt)->getRealTime())) {
  98. break;
  99. }
  100. // unmerged drop message
  101. if (dropped) {
  102. last.add(e);
  103. if ((!gc && (e->getUid() == worst))
  104. || (mLastWorstUid[id].find(e->getUid())
  105. == mLastWorstUid[id].end())) {
  106. mLastWorstUid[id][e->getUid()] = it;
  107. }
  108. ++it;
  109. continue;
  110. }
  111. if (e->getUid() != worst) {
  112. leading = false;
  113. last.clear(e);
  114. ++it;
  115. continue;
  116. }
  117. pruneRows--;
  118. if (pruneRows == 0) {
  119. break;
  120. }
  121. kick = true;
  122. unsigned short len = e->getMsgLen();
  123. // do not create any leading drops
  124. if (leading) {
  125. it = erase(it);
  126. } else {
  127. stats.drop(e);
  128. e->setDropped(1);//setDropped函数,这里就是普通的是worst这个uid的log,但不是黑名单中。就把它的消息清空
  129. if (last.merge(e, 1)) {//合并
  130. it = erase(it, false);//合并之后,删除现有log
  131. } else {
  132. last.add(e);
  133. if (!gc || (mLastWorstUid[id].find(worst)
  134. == mLastWorstUid[id].end())) {
  135. mLastWorstUid[id][worst] = it;
  136. }
  137. ++it;
  138. }
  139. }
  140. if (worst_sizes < second_worst_sizes) {//最差值小于第二差就跳过
  141. break;
  142. }
  143. worst_sizes -= len;
  144. }
  145. last.clear();
  146. if (!kick || !mPrune.worstUidEnabled()) {
  147. break; // the following loop will ask bad clients to skip/drop
  148. }
  149. }
  150. .....

上面就是对黑名单以及log最多的那个uid的处理,我们先来看看每个LogBufferElement的setDropped函数

[cpp] view plaincopy
  1. unsigned short setDropped(unsigned short value) {
  2. if (mMsg) {
  3. free(mMsg);//消息清空
  4. mMsg = NULL;
  5. }
  6. return mDropped = value;//第一次为1
  7. }

这个函数直接把消息清空了,然后把mDropped设为1,我们再来看看last.merge(e, 1)函数

[cpp] view plaincopy
  1. class LogBufferElementLast {
  2. typedef std::unordered_map<uint64_t, LogBufferElement *> LogBufferElementMap;
  3. LogBufferElementMap map;
  4. public:
  5. bool merge(LogBufferElement *e, unsigned short dropped) {
  6. LogBufferElementKey key(e->getUid(), e->getPid(), e->getTid());
  7. LogBufferElementMap::iterator it = map.find(key.getKey());
  8. if (it != map.end()) {
  9. LogBufferElement *l = it->second;
  10. unsigned short d = l->getDropped();
  11. if ((dropped + d) > USHRT_MAX) {
  12. map.erase(it);
  13. } else {
  14. l->setDropped(dropped + d);//将两个element合并
  15. return true;
  16. }
  17. }
  18. return false;
  19. }

通过merge,element的mDropped可以不为1了。

3.3 白名单处理

下面我们再看下白名单处理:

[cpp] view plaincopy
  1. bool whitelist = false;
  2. bool hasWhitelist = mPrune.nice();
  3. it = mLogElements.begin();
  4. while((pruneRows > 0) && (it != mLogElements.end())) {
  5. LogBufferElement *e = *it;
  6. if (e->getLogId() != id) {
  7. it++;
  8. continue;
  9. }
  10. if (oldest && (oldest->mStart <= e->getSequence())) {
  11. if (whitelist) {
  12. break;
  13. }
  14. if (stats.sizes(id) > (2 * log_buffer_size(id))) {
  15. // kick a misbehaving log reader client off the island
  16. oldest->release_Locked();
  17. } else {
  18. oldest->triggerSkip_Locked(id, pruneRows);
  19. }
  20. break;
  21. }
  22. if (hasWhitelist && !e->getDropped() && mPrune.nice(e)) { // WhiteListed
  23. whitelist = true;
  24. it++;
  25. continue;
  26. }
  27. it = erase(it);
  28. pruneRows--;
  29. }

白名单的处理比较简单,只要是白名单的不删除,其他都删除,直到满足条件。

四、logcat取log

之前的博客分析过当logcat进程到logd中取log时,会最终调用LogBufferElement::flushTo函数

[cpp] view plaincopy
  1. uint64_t LogBufferElement::flushTo(SocketClient *reader, LogBuffer *parent) {
  2. struct logger_entry_v3 entry;
  3. memset(&entry, 0, sizeof(struct logger_entry_v3));
  4. entry.hdr_size = sizeof(struct logger_entry_v3);
  5. entry.lid = mLogId;
  6. entry.pid = mPid;
  7. entry.tid = mTid;
  8. entry.sec = mRealTime.tv_sec;
  9. entry.nsec = mRealTime.tv_nsec;
  10. struct iovec iovec[2];
  11. iovec[0].iov_base = &entry;
  12. iovec[0].iov_len = sizeof(struct logger_entry_v3);
  13. char *buffer = NULL;
  14. if (!mMsg) {//如果mMsg为null了,就是之前prune里面setPropped函数把mMsg设为null
  15. entry.len = populateDroppedMessage(buffer, parent);
  16. if (!entry.len) {
  17. return mSequence;
  18. }
  19. iovec[1].iov_base = buffer;
  20. } else {
  21. entry.len = mMsgLen;
  22. iovec[1].iov_base = mMsg;
  23. }
  24. iovec[1].iov_len = entry.len;
  25. uint64_t retval = reader->sendDatav(iovec, 2) ? FLUSH_ERROR : mSequence;//发送给调用者
  26. if (buffer) {
  27. free(buffer);
  28. }
  29. return retval;
  30. }

调用populateDroppedMessage函数最终会把消息设为类似:

[html] view plaincopy
  1. chatty  : uid=1000(system) RenderThread expire 3 lines

五、总结 & 解决方案

最后总结,在logd中如果有丢失log,可以设置log_id的缓冲设置再大写。如果是调试的话可以增加调试的白名单。而且在logd中丢失log肯定会有类似chatty这样的log,那就是删除了log最多的那个uid的log。而且会合并。

我们可以通过设置系统属性persist.logd.size来设置每个log id的最大缓存值(在开发者选项中也有这个设置,开发者选项中设置就不用重启设备了),或者persist.logd.size.radio设置每个id的最大缓存值。

步骤:

  1. 将手机连上电脑并且进入root

  2. setproppersist.logd.size.radio 1024k

  3. reboot 重启

另外可以用getprop | grep logd查看设置的属性是否生效

logcat -g 可以查看每个id 的缓存大小

当然这是通过属性的方法设置,我们还可以通过logcat的命令,logcat -G 10m是设置所有的id的大小,logcat -b radio -G 10m是设置radio的log的缓存大小

在logcat中有如下代码,处理设置缓存大小

如果logd中没有chatty这样的log,但是又有log丢失,那么就要怀疑在写log时,logdw的socket就有丢失。因为我们看下logdw是dgram类型的,这种socket是一种不可靠的报文传递保证效率但会有丢失。所有这样情况我们可以看把socket改成stream试试,看看是否有效果?

[html] view plaincopy
  1. service logd /system/bin/logd
  2. class core
  3. socket logd stream 0666 logd logd
  4. socket logdr seqpacket 0666 logd logd
  5. socket logdw dgram 0222 logd logd
  6. group root system
  7. writepid /dev/cpuset/system-background/tasks

但是试了以后好像socket在连接的时候就有问题。

后续我们使用android4.4的的机制 kernel的log机制,这样就不会有丢失问题。



原文地址: http://blog.csdn.net/kc58236582/article/details/51506896

android log丢失(一)使用logd丢失log原理相关推荐

  1. mysql redo log 数据恢复_MySQL 怎么样恢复丢失的数据?redo log 写磁盘的过程

    在生活中,你一定有过类似这样的经历: 比如部门发礼品.或者说学校发课本.如果在发放的时候,大家一窝蜂的涌了过来,毕竟双拳双敌四手,渐渐你就招架不过来. 为了工作更好做,你会有几个选择,提前打印个名单, ...

  2. android 6.0 logcat机制(一)java层写log,logd接受log

    第一篇博客,讲的主要是c++,java中打印log,然后通过socket传给logd,然后logd是如何处理接受log的. 一.logcat常用命令 logcat -c 清除已有log信息 logca ...

  3. android webview 设置cookie时间,解决Android webview设置cookie和cookie丢失的问题

    Android页面嵌套了一个h5,H5页面内部有用户登陆页面,发现h5页面的登陆功能无法使用,一直登陆失败.和web那边商量一会,发现js写入的cookie丢失了.所有需要Android这边在重写写入 ...

  4. logd 删除log

    进程Log过多,超过上限,导致log被删除,类似: chatty  : uid=1000(system) MountService expire 168 lines logd  删除log的判断依据为 ...

  5. Android流媒体开发之 自定义一个完备的log模块

    Android音视频开发之 自定义一个完备的log模块 前言 基础知识的掌握 Log系统 为什么需要自定义一个log模块呢? 做什么? 怎么做? 确定成员变量: 初始化LogUtil 输出功能的实现 ...

  6. Android studio 使用androidX后应用启动log中报Didn‘t find class “android.view.View$OnUnhandledKeyEventListener“

    Didn't find class "android.view.View$OnUnhandledKeyEventListener" Android studio 使用android ...

  7. 如何从 Android 手机恢复已删除或丢失的照片

    当 Android 上的照片被意外删除时,您一定会感到难过,不知道该怎么办,尤其是那些珍贵的照片.实际上,现在您不必太担心,因为您可以利用多种方法来恢复 Android 手机上已删除或丢失的照片.其实 ...

  8. android adb命令 抓取系统各种 log

    android adb命令 抓取系统各种 log getLog.bat: adb root adb remount adb wait-for-device adb logcat -v time > ...

  9. Android L日志系统1——logd

    在介绍完Android M之前的日志系统的实现之后,我们现在来看看现在最新的Android L的日志机制.Android L与之前版本最大的变化,就是日志保存的位置由Kernel的Ringer Buf ...

最新文章

  1. 51单片机实现对24C02进行页写、顺序读取并显示验证
  2. 华胜天成ivcs云系统初体验2
  3. Python3 基础语法(笔记2)
  4. 2012 RDS Remote App 对于win7的支持问题
  5. 检查mysql当前状态
  6. 从零开始入门 K8s | 应用存储和持久化数据卷:核心知识
  7. 【狂人小白】如何将Java项目发布到Maven中
  8. (创建模式 上)设计模式——工厂、抽象工厂 C++/Python3实现
  9. MySQL数据库和ACID模型
  10. (转载)WebSphere MQ安装过程
  11. 简单的用户登录(一)
  12. DXUT实战2:HLSL(withoutEffect)+D3D9+DXUT(june_2010) .
  13. c语言 学习手册,c语言学习手册
  14. 使用boost线程池很好的例子
  15. RFID 射频识别技术 NFC ISO14443 - A 协议 RC522
  16. java邮件抄送_JAVA实现邮件抄送,密送,多个附件发送
  17. 电信机顶盒时中心服务器异常,电信机顶盒常见故障汇总大全
  18. PAMI-2021:5篇顶级GNN论文
  19. telnet协议的Wireshark抓包分析
  20. 华为荣耀android是什么系统,华为荣耀+

热门文章

  1. JQuery图片切换 Win8 Metro风格Banner
  2. Cognos 云最佳实践: 调整架构提供性能和可伸缩性
  3. Python学习笔记:Dict和Set
  4. 时频分析:短时傅立叶变换实现(4)
  5. Linux下远程连接断开后如何让程序继续运行
  6. 使用Linux命令来发送信息
  7. 根据centos系统启动过程定位故障位置
  8. 你一定要知道的关于Linux文件目录操作的12个常用命令
  9. github本地文件和远端文件的协同
  10. 神经网络网络参数的取值问题