谢谢
Trace. User-space + Kernel-space. 本质上是 log,不过是编译器和 kernel 生成的。
有人写的 log 那更好,特别是输出一些全局变量(系统状态)。
>
> 2009/11/29 Shuo Chen <gian...@gmail.com>
>>
>> 先说说你的想法。
>> 你"认为分布式调试的应该至少提供哪些功能"?
>>
>> On Nov 29, 6:06 pm, raymond <shiqu...@gmail.com> wrote:
>> > 分布式调试是一个颇为困难的话题。
>> > hadoop是一款开源的分布式系统,其上面提供了一点点分布式调试方面的用法,但是我感觉多集中在细节层面的处理,比如方便查看log文件,重新启动
>> > 一个失败的instance等等,但是缺乏统一的系统层面的调试框架。
>> > 大家能否介绍一下对分布式调试框架的感受,比如,
>> > 开源/产品,分布式调适框架
>> > 很好的介绍分布式调试的文章
>> > 分布式调试网站
>> > 个人认为分布式调试的应该至少提供哪些功能
>> >
>> > 谢谢
>
>
> --
> 《采莲》·江南
>
> 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> 另抄自蒜头的评论:http://www.douban.com/review/1573456/:
>
> 且祭一束紫琳秋,为一段落花流水的传说
> 且饮一杯青花酒,为一场几多擦肩的错过
> 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
--
黄 澗石 (Jianshi Huang)
http://huangjs.net/
多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。当然调试器也会有类似的影响。
多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。当然调试器也会有类似的影响。
有本质区别,一个是人写,信息不全,一个是机器帮你生成,无遗漏,信息全(函数调用程度上)。
> 而另一方面我担心的是,既然trace需要依赖内核的支持,如果楼主设想的场景中一个分布式系统需要跑在不同的操作系统上,我怎么保证不同系统给出来的trace保持相同的格式?
>
不是所有的 kernel tracing 都需要内核支持,如果你只想知道 syscall 的话,类似 strace
就可以了。至于跨平台跑分布式。。。还是在编译器里搞吧,我没经验。
2009/11/30 Fuzhou Chen <cppo...@gmail.com>:> 既然trace本质上还是log,那么这不算是“比log更方便“的东西。
>
有本质区别,一个是人写,信息不全,一个是机器帮你生成,无遗漏,信息全(函数调用程度上)。
> 而另一方面我担心的是,既然trace需要依赖内核的支持,如果楼主设想的场景中一个分布式系统需要跑在不同的操作系统上,我怎么保证不同系统给出来的trace保持相同的格式?
>
不是所有的 kernel tracing 都需要内核支持,如果你只想知道 syscall 的话,类似 strace
就可以了。至于跨平台跑分布式。。。还是在编译器里搞吧,我没经验。
--
> 恐怕我的看法恰恰相反:我认为机器生成的东西遗漏的信息太多所以不可信任。因为实际调试中我们需要的信息并不仅仅是函数调用程度,而是需要更细致的定制信息。比如精确到某个变量在进入某个if语句的if块和else块时的取值。而且程序员也需要输出一些其他的信息用来作为提示而用。
>
你说的局部变量的问题可以用 backtrace 显示,和有没有tracing 信息无关。如果你用到全局变量,可以用局部变量引用一下,照样可以有很全的信息。
信息是越多越好,不过我不认为哪些信息必须用人工才能输出,能否举个例子?
> 就我工作上所知,Windows自Vista开始的很多系统服务dll内部的确有这个trace,但首先它还远远没有精确到这个程度,而且就我接触的部分而言这些trace也一样是写那些DLL的程序员写在代码里的,只不过是用一个通用的log框架记录在系统某处,然后还需要靠一些专门的工具
>
跑到 kernel space 里,没kernel space 的调试工具你的程序自然啥也干不了。
> 莫非Linux下现在已经有能智能分析指定变量在所有分支上的当前值的工具了?我很多年没读Linux内核了,如果有的话,可否给个连接?
这就是 linux 的好处,你可以自己编译内核,在指定的地方加入你要的 probe 不就可以了?有 linux
内核的高手吗?能否说明一下。我个人暂时还没有项目需要我调试内核。不过对于 SA 来说,Kernel Tracing + Flight
Record 是很有用的技术。
>> 不是所有的 kernel tracing 都需要内核支持,如果你只想知道 syscall 的话,类似 strace
>> 就可以了。至于跨平台跑分布式。。。还是在编译器里搞吧,我没经验。
>
> 唉,做应用开发的我研究syscall干什么?其实真的调试时候我们最关心的往往是我们自己的代码里哪个变量在某个位置的值是什么,它之前经过了几个while几个if,如果某个系统的trace能替我们自动做这个的话,我还真的抓住阁下非要问个明白了,这对我这种管维护的人来说可是福音。
>
If you do network programming ... syscall tracing can be useful.
当然,不是说就没有其他方法,既然系统提供了那么方便的工具,干嘛不用?
你做管理的?建议你看看南京 Fujitsu 他们在做的 Flight Record (黑匣子)项目,就是说系统 crash 的时候把
kernel 的时序上一定时间内的log保存下来。
> 恐怕我的看法恰恰相反:我认为机器生成的东西遗漏的信息太多所以不可信任。因为实际调试中我们需要的信息并不仅仅是函数调用程度,而是需要更细致的定制信息。比如精确到某个变量在进入某个if语句的if块和else块时的取值。而且程序员也需要输出一些其他的信息用来作为提示而用。你说的局部变量的问题可以用 backtrace 显示,和有没有tracing 信息无关。如果你用到全局变量,可以用局部变量引用一下,照样可以有很全的信息。
>
信息是越多越好,不过我不认为哪些信息必须用人工才能输出,能否举个例子?
跑到 kernel space 里,没kernel space 的调试工具你的程序自然啥也干不了。
> 就我工作上所知,Windows自Vista开始的很多系统服务dll内部的确有这个trace,但首先它还远远没有精确到这个程度,而且就我接触的部分而言这些trace也一样是写那些DLL的程序员写在代码里的,只不过是用一个通用的log框架记录在系统某处,然后还需要靠一些专门的工具
>
> 莫非Linux下现在已经有能智能分析指定变量在所有分支上的当前值的工具了?我很多年没读Linux内核了,如果有的话,可否给个连接?
这就是 linux 的好处,你可以自己编译内核,在指定的地方加入你要的 probe 不就可以了?有 linux
内核的高手吗?能否说明一下。我个人暂时还没有项目需要我调试内核。不过对于 SA 来说,Kernel Tracing + Flight
Record 是很有用的技术。
No, 我的开发语言是 Common Lisp,编译器也不是 gcc。但是做法是类似的。
至于何时调用 backtrace? 那当然是程序出错的时候。比如某个函数的 precondition
不满足,资源耗尽,等。这个时候我一般输出资源使用情况(这个其实定期会输出) + backtrace。问题是,backtrace (或者
dump)只包含当前的状态,有些情况下比如多线程有racing情况下你是无法只用它来找出原因的。
所以 tracing 就很有用了。tracing 对理解程序的行为也是很有帮助的。
> 另外我们说了这么就trace,jianshi可否给个列表说一下你说的这个东西是需要什么工具的?跑在哪个平台上?
>
我自己其实在做一个 Common Lisp (Linux) 平台下的 flight record。暂时用到的工具是:
- Common Lisp 的 trace (implementation dependent)
- Strace
- SystemTap
有专门的进程负责收集 log,保存到 ring-buffer。现在的问题是 overhead 太大,不过能用是关键。
也在看 LTTng,不过没什么进展。
我所有的开发都在 Linux 下。
>
> 跑题一下,系统服务不等于kernel啊,比如Windows DNS和Active
> Directory就有这个trace,只是一个daemon级别的service而已。但是它写的log是留在系统里看不见的,需要一些工具dump出来并且转成文本格式。这个月调这个东西,里面给的trace信息太少,痛苦得要死。
>
原来如此,closed source 就没办法了,你问问微软看看有没有 log 版本。
> 其实自己编译内核也没解决我说的问题。商业软件开发的典型模式是自己内部测好了就交给用户用,我们总不能要求用户帮我编译一个内核吧?而且如果说是出了问题重新编译一个内核再加上一些probe,那和Debug/Release双版本模式有什么区别呢?我怎么知道问题不会因为我加了几个probe而无法重现?
>
说实在的,尚未用到需要重新编译内核才能开启的功能。tracing 和 log 一样,目的是帮你 locate
问题代码片段。有了输入输出再加一点时序,开始读代码吧。就如 log 一样,trace 什么内容对性能有很大影响,自己看适合不适合吧。
>
>
> --
> 《采莲》·江南
>
> 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> 另抄自蒜头的评论:http://www.douban.com/review/1573456/:
>
> 且祭一束紫琳秋,为一段落花流水的传说
> 且饮一杯青花酒,为一场几多擦肩的错过
> 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
--
2009/11/30 Fuzhou Chen <cppo...@gmail.com>:
> 敢问Jianshi说的可是GNUNo, 我的开发语言是 Common Lisp,编译器也不是 gcc。但是做法是类似的。
> glibc的backtrace()函数?如果是这个的话,那问题就没有解决:程序员仍然要自己决定在程序的哪里加这个调用,需要多频繁。
>
至于何时调用 backtrace? 那当然是程序出错的时候。比如某个函数的 precondition
不满足,资源耗尽,等。这个时候我一般输出资源使用情况(这个其实定期会输出) + backtrace。问题是,backtrace (或者
dump)只包含当前的状态,有些情况下比如多线程有racing情况下你是无法只用它来找出原因的。
所以 tracing 就很有用了。tracing 对理解程序的行为也是很有帮助的。
> 另外我们说了这么就trace,jianshi可否给个列表说一下你说的这个东西是需要什么工具的?跑在哪个平台上?
>
我自己其实在做一个 Common Lisp (Linux) 平台下的 flight record。暂时用到的工具是:
- Common Lisp 的 trace (implementation dependent)
- Strace
- SystemTap
有专门的进程负责收集 log,保存到 ring-buffer。现在的问题是 overhead 太大,不过能用是关键。
也在看 LTTng,不过没什么进展。
我所有的开发都在 Linux 下。
原来如此,closed source 就没办法了,你问问微软看看有没有 log 版本。
>
> 跑题一下,系统服务不等于kernel啊,比如Windows DNS和Active
> Directory就有这个trace,只是一个daemon级别的service而已。但是它写的log是留在系统里看不见的,需要一些工具dump出来并且转成文本格式。这个月调这个东西,里面给的trace信息太少,痛苦得要死。
>
说实在的,尚未用到需要重新编译内核才能开启的功能。tracing 和 log 一样,目的是帮你 locate
> 其实自己编译内核也没解决我说的问题。商业软件开发的典型模式是自己内部测好了就交给用户用,我们总不能要求用户帮我编译一个内核吧?而且如果说是出了问题重新编译一个内核再加上一些probe,那和Debug/Release双版本模式有什么区别呢?我怎么知道问题不会因为我加了几个probe而无法重现?
>
问题代码片段。有了输入输出再加一点时序,开始读代码吧。就如 log 一样,trace 什么内容对性能有很大影响,自己看适合不适合吧。
几位兄弟在讨论log的时候主要还是在讨论单线程或者单击多线程问题,讨论的重点也是经典问题:
1. log要不要?
2. log如何产生?系统来做还是用户来做?
3. 执行顺序和变量状态哪个更有利于debug?这恰好反应两位的不同工作场景
关于上面分布式是多线程还是多进程的问题
分布式显然是多进程的,但是当我们在单机上进行分布式调试的时候,是用多线程模拟的,我们总不可能人人配备集群来debug程序。
我假定的分布式模型是MAP-REDUCE,假设就在hadoop上跑程序。
工作场景如下:
100台机器,1000个process,数据将流过task A,然后流过task B,然后是task C,最后结束。A,B,C相当于是三类进
程,不是特定的进程。
现在B的某个进程出现了问题
1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
可能。。。,
问题2跟问题1颇为类似,但是不同。问题1是说如何构建分布式debug框架,属于宏观问题。问题2是说,即使有了分布式调试框架,应该记录哪些信息,
是微观问题。
我知道某些general的解决方案,写输出,读输出,这肯定能解决,而且无论多么高效优雅的解决方案,其底层也一定是这么干的,但是,那个比较高效优
雅的方案在哪里?
On 11月30日, 下午5时37分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 2009/11/30 Jianshi Huang <jianshi.hu...@gmail.com>
>
> > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>:
1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
可能。。。,
我现在做的服务器有类似的功能, 我们管它叫作 replay, 需要保存的东西
1. 所有 os platform api, 包括 文件, ioctl, 等等
这就包括了, 和其他进程的通信, 现在我们用的是 tcp socket
2. 未必能够重复的 c library function 例如, rand
3. 所有时间相关的东西, 包括所有时间相关的系统调用, 以及所有被封装的 rdtsc 调用, 需要专门处理.
这个需要专门的处理, 包括获取时间(当前时刻时间值, 时间片长度), 以及sleep
4. 我们现在的程序没有, 但是如果其他程序有的话, 也需要处理的: 和其他进程进行同步(例如 mutex 等)
另外, 程序还不能是多线程的, 因为 replay 过程中, 你无法控制多线程的调度次序.
最麻烦的不是保存数据, 而是 程序的时间控制/主调度框架, 因为这和正常执行毕竟是不一样的, 只要有一点点区别, 由于蝴蝶效应, 后面就
会完全不一样了.
这个方法还有一个麻烦的问题在于 (大概只是对于 C/C++), linux clib 每次跑, malloc 分配的地址空间是不同的
(大概是堆扰乱防止容易被攻击? 但是我没有看到取消点), 某些非法访问的操作, replay 过程中, 未必会在同一个地方出现, 如果提早
出现了还好, 如果晚出现了, 这次 replay 可能就不出错了. ( 这个问题我还没有深入分析, 如果是 c library 造
成, 可以用替换 malloc 库的方法解决, 如果是 linux kernel 造成, 可能就需要想更深入的方法了.)
> 我的观点很简单:
>
> a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> b) 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> + Code Coverage + Fuzzying。
> c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
确实如此.
> > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
如果面向大众的服务器, 呵呵, 这也是不能做到的.
>
> 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。
说得好 :)
我也是因为好几个 log 在开发环境无法重现, 而在生产环境时不时冒出来, 才费力去开发一套 replay 机制, replay 也是
挺费时间的, desktop cpu + overclock 比生产环境快不到 50%, 生产环境平均 cpu 利用率 50%, 那么生产环境
跑6天出的错误, replay 环境需要跑完第二天才能到, 挺费力的一件事情.
> 关于如何设计多机调试框架的问题,我知道的一些项目基本上还是依赖于log,而他们的设计则比我们常用的log复杂很多。我曾经听一个同事说起过某个杀毒软件公司(似乎是瑞星)的内部log框架,他们的log不是直接写在本机上的,而是每个进程有一个独立的线程来收取log,然后各个log线程会把信息发送给一个中央的服务器,由中央服务器根据时间戳把所有的log组合成一个单独的文件。那个框架用了很多buffer机制来避免log输出对功能模块造成额外的性能开销。不过我没有自己用过,效果如何难以评价。
log 的性能问题, 我觉得只要不是在hotspot 处, 都不应该是问题, 如果检查到已经造成拖累了, 那么可以开发一个 log
process, 和主进程通过 lock free ring buffer 通信, 这样的话, 主要的开销其实只是主进程里面的格式化而已
( printf or io stream).
至于 hotspot 哪里, 代码量至少不会很大, 再review 几次 code, 去掉 log 问题也不大啊. 如果是要加上错误
情况检查, 那么写一个为 cpu 分支预测考虑的检查代码, 开销应该也不会很大的. ( gcc 学习 linux kernel 的
likely, unlikely, vc 没有这个, 就能将常见情况写作肯定分支了).
log 的性能问题, 我觉得只要不是在hotspot 处, 都不应该是问题, 如果检查到已经造成拖累了, 那么可以开发一个 log
process, 和主进程通过 lock free ring buffer 通信, 这样的话, 主要的开销其实只是主进程里面的格式化而已
( printf or io stream).
至于 hotspot 哪里, 代码量至少不会很大, 再review 几次 code, 去掉 log 问题也不大啊. 如果是要加上错误
情况检查, 那么写一个为 cpu 分支预测考虑的检查代码, 开销应该也不会很大的. ( gcc 学习 linux kernel 的
likely, unlikely, vc 没有这个, 就能将常见情况写作肯定分支了).
> > ----所以我特别讨厌release版本里把log去掉的做法,偏偏这却是很多项目开发的"共识"。
On 11/29/09, raymond <shiq...@gmail.com> wrote:
> 分布式调试是一个颇为困难的话题。
> hadoop是一款开源的分布式系统,其上面提供了一点点分布式调试方面的用法,但是我感觉多集中在细节层面的处理,比如方便查看log文件,重新启动
> 一个失败的instance等等,但是缺乏统一的系统层面的调试框架。
> 大家能否介绍一下对分布式调试框架的感受,比如,
> 开源/产品,分布式调适框架
> 很好的介绍分布式调试的文章
> 分布式调试网站
> 个人认为分布式调试的应该至少提供哪些功能
>
> 谢谢
>
--
Sent from my mobile device
Regards,
Linker Lin
linker...@gmail.com
On 11月30日, 下午2时37分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 2009/11/29 Tinyfool <tinyf...@gmail.com>
>
> > 多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
> > 当然调试器也会有类似的影响。
>
> 正是因为这种影响的存在,所以我才说log不是想加就加想去掉就去掉的,而是必须在系统设计的时候作为设计的一部分进行考虑,什么地方可以用,什么地方不
> 能用,都必须有一个明确的设计决定。
>
> 从这个意义上来说,在我看来一些非常通用的编程惯例都是有问题的,比如编译一个带log的debug版本和一个不带log的release版本,然后把release版本发布给用户。我们怎么知道两个版本的行为一定是相同的?事实上我在工作中也确实见到很多因为这个原因最后不得不不了了之的bug报告。
>
> 如果非要较这个真的话,不妨想想看在一个多线程——好吧大家似乎都很喜欢把“分布式”偷偷换成这个概念,我只好跟了,幸亏对大部分讨论没有影响——加一个断点对时序的影响吧,整个消息队列的行为就可能被完全改变。
>
> 而且就如我说的,调试器本身还会带来一个部署的问题,很多情况下生产环境下并不允许随便装软件的,而且对于那种在复杂条件下才能触发的bug,难道我们能要求用户运行程序的时候永远开着调试器么?
>
>
>
> > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>
>
> >> 在我的经验中合理编写的log能更有效地反映程序当时执行的次序。如果log写得比较好那么一般是犯不着麻烦调试器出马的。
>
> >> 多进程(包括单机和多机)难以调试的多数原因往往是开发人员不会写或者干脆就是懒得写log导致出问题时无法得到正确的执行和交互逻辑。而多进程程序中因为各种竞争条件之类引起的bug难以重现的问题要比单机程序严重得多。所以我在公司交接任何项目时第一个会问的问题永远是:log在哪里?
>
> >> 如果楼主认为还有比log更方便的东西,在下洗耳恭听。
>
> >> 2009/11/29 Shuo Chen <giantc...@gmail.com>
>
> >> 先说说你的想法。
> >>> 你"认为分布式调试的应该至少提供哪些功能"?
>
> >>> On Nov 29, 6:06 pm, raymond <shiqu...@gmail.com> wrote:
> >>> > 分布式调试是一个颇为困难的话题。
> >>> > hadoop是一款开源的分布式系统,其上面提供了一点点分布式调试方面的用法,但是我感觉多集中在细节层面的处理,比如方便查看log文件,重新启动
> >>> > 一个失败的instance等等,但是缺乏统一的系统层面的调试框架。
> >>> > 大家能否介绍一下对分布式调试框架的感受,比如,
> >>> > 开源/产品,分布式调适框架
> >>> > 很好的介绍分布式调试的文章
> >>> > 分布式调试网站
> >>> > 个人认为分布式调试的应该至少提供哪些功能
>
> >>> > 谢谢
>
> >> --
> >> 《采莲》·江南
>
> >> 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> >> 另抄自蒜头的评论:http://www.douban.com/review/1573456/:
>
> >> 且祭一束紫琳秋,为一段落花流水的传说
> >> 且饮一杯青花酒,为一场几多擦肩的错过
> >> 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> >> 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
> > --
> > Tinyfool的开发日记http://www.tinydust.net/dev/
> > 代码中国网http://www.codechina.org
正如你说的,当用户机器慢时没有问题(暂且认为正常),当用户机器升级后暴露了问题,如果有日志,正好可以帮助进行定位,为什么不呢?
在这样大系统中,合理设计大log系统并不会成为系统的瓶颈吧。
On 11月30日, 下午3时05分, Tinyfool <tinyf...@gmail.com> wrote:
> 没有log就会产生的竞争条件,不解决往往是很危险的。因为在某机器上log的效率下降恰好把竞争条件掩盖了,而随着用户机器的升级,更快的cpu和io下,这些竞争条件可能还会重新出现。所以,debug有log没有问题,那就给release版本也加上log来解决竞争条件是非常不负责任的。而且,对复杂的程序来说,调试log带来的性能下降是不能忽略的,这就是为什么调试log仅应该在debug的时候存在。
>
> 2009/11/30 Fuzhou Chen <cppof...@gmail.com>
>
>
>
> > 恐怕殷兄的说法恰恰证明了只有log才能解决问题,而调试毫无办法。
>
> > 这里最大的问题是:为什么release版本就不输出log?如果release版本因为有了log而导致问题不再产生,那么这个bug报告就根本不会出现;如果release版本有了log仍然能出现,那么我们从release版本的log就能看到我们需要的信息,我们为什么还需要debug版本?
>
> > 2009/11/29 殷远超 <yinyuanc...@gmail.com>
>
> >> 对头,我这曾经有个问题,就是release版有问题,debug版始终没有,调试无法重现。后来发现log降低效率之后,完全不产生竞争条件,对于共享内存的模型,线程/j进程安全绝对不是log能解决的。
>
> >> 2009/11/30 Tinyfool <tinyf...@gmail.com>
>
> >>> 多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
> >>> 当然调试器也会有类似的影响。
>
> > --
> > 《采莲》·江南
>
> > 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> > 另抄自蒜头的评论:http://www.douban.com/review/1573456/:
>
> > 且祭一束紫琳秋,为一段落花流水的传说
> > 且饮一杯青花酒,为一场几多擦肩的错过
> > 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> > 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
> --
On 11月30日, 下午3时37分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 2009/11/29 Jianshi Huang <jianshi.hu...@gmail.com>
>
> > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>:
>
1. 你是把所有的重要调用过程全部记录下来,然后replay的时候,按照顺序重新调用吗?比如,之前发送了一个消息,现在也要发送一个一样的;之前
做了排序,现在也做一遍?如果是这样,这个replay框架是不是非常大和复杂?是不是要支持很多类似反射的东西?是不是针对每个功能点还要写支持
replay附加操作?
2. 需要保存的东西包括:
平台api,输入输出文件,消息,方法参数,时间序列。保存这些元素的格式方面有什么需要注意的吗?
还有一个问题想问,我们现在的log格式多是
时间,级别,调用方法,代码行,线程id,二进制文件。我感觉要想达到replay的功能还差的很远,那么,在log格式的设计上,还请兄弟给点建
议。
能不能举个贯通的例子说明一下。十分感谢。
看了一下介绍,iDNA基本是调试单机程序的,而且应该适合调试那种逻辑多,数据少的程序,而分布式调试一般处理的是逻辑不多,数据量大的程序。
On 12月1日, 上午3时58分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 呵呵怎么着我也是把问题带跑的那个人,既然楼主都发话了我们还是回到正题吧。
>
> 首先我得说我没有做MapReduce类型应用的经验,我现在的工作集中在Windows活动目录这一块,特征是低平均通信强度下的服务器LDAP数据库信息同步。所以我的观点基本上基于我的背景,可能和楼主期望的场景有所不同,如果有不同意见欢迎指出。
>
> 2009/11/30 raymond <shiqu...@gmail.com>
>
>
>
> > 1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
> > 时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
>
> 我的观点很简单:
>
> a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> b) 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> + Code Coverage + Fuzzying。
> c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
>
>
>
> > 2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
> > 可能。。。,
>
> > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
>
> 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。
你想想, 软件无非是一个状态机(有限或者无限状态就不知道了), 我每次给出同样的输入内容, 他应该给出同样的输出.
所以, 记录初次运行你给他的所有输入, 以及它给出的输出; replay 的时候, 再次给它同样的输入, 看看输出是否有变就知道了.
"之前发送了一个消息" 被测软件的发送? replay 的时候, 这样的东西, 不是用来调用的, 是用来检查和初次运行的结果是否相同的.
排序? 这种东西不是程序边界的东西, 你管它干什么? 除非你告诉我, 你是通过 com/xpcom 调用一个外部程序来排序.
你只找出程序和程序之外的所有边界api, 看看哪些边界api 调用, 结果是依赖于被测软件之外的因素, 不可以重复的, 控制这些api.
例如我们认为 c lib 是外部, 那么 snprintf 是可以重复的, 每次给同样的参数, 它会给同样的输出, 这个api 就不用控制,
gettimeofday/timeGetTime 这种是不能够重复的, 你给它同样的参数, 它不保证同样的输出, 你就需要在初次运行的时候保
存 参数, 结果, errno, replay 的时候, 给出同样的结果, 设置同样的 errno 不用调用这个 api.
被控制的函数, 调用被 redirect 到 normal run OR run & save OR load and return 的
代码, 这样, 一套代码就可以在几个模式下运行了. 我用 C++, 没有反射可以用, 也用不着, 将被控制的函数改成 函数指针 OR
interface 的一个成员就 ok 了.
这已经很细节了, 再详细就到代码了, 说来就没有意思了.
>
> 2. 需要保存的东西包括:
> 平台api,输入输出文件,消息,方法参数,时间序列。保存这些元素的格式方面有什么需要注意的吗?
>
> 还有一个问题想问,我们现在的log格式多是
> 时间,级别,调用方法,代码行,线程id,二进制文件。我感觉要想达到replay的功能还差的很远,那么,在log格式的设计上,还请兄弟给点建
> 议。
我们的软件中, replay log 和 log 是两套东西, 一套给机器用, 一套给人看. 当然, 也可以从 replay
log 中进行业务分析.
replay log 是直接存struct 的, 避免解析字符串. 同时 replay log 很大, 每512M 就开新文件, 同时后
台用7zip压缩旧文件 (就这样, 一天还会有二十多G 压缩结果).
save 的时候, 对我们的系统, 这套机制大概要消耗 10% 的 cpu 处理能力, 还能够接受.
> 能不能举个贯通的例子说明一下。十分感谢。
我觉得上面已经很清楚了.
调试器对解决有时序要求的问题, 都没有什么作用, 除非时间粒度非常大. 对于调试竞态问题, 也没有用.
对于稍复杂的系统, 特别是分布式的, 时序相关的问题肯定是存在的, 这部分问题, 调试器自然没有用.
而时序, 竞态问题, 正属于难处理的问题, 调试器都发挥不出用处.
对于调试器可以发挥作用的问题, 每次在调试器上单步, watch 消耗的精力, 都是一次性的, 没有积累的, 曾经花费的努力, 对于以后的质量
保证没有任何帮助.
而如果将这些精力里面的大部分, 转移到单元测试, 覆盖测试, 自动测试, 自查错, log 代码上, 就是有积累的, 以后程序越来越容易保证质
量.
我见过有些程序员超级喜欢打日志,但是实际上这些日志做的就是调试器做的工作----看变量,然后看到日志刷得到处都是,真想找点有用的信息的时候,
很长的"!!!!!!!!!!!"或者'****************'满天飞,有时候还得grep来grep去,麻烦的很。
另外我觉得windows程序的调试很方便,尤其是运行时设置断点的功能
嘿嘿,UP 之前和我吵过一次架,上次是我说话他抬杠,不过这一次看来我必须得抬一次杠了。
接下来我说话会很重,但请相信我绝无人身攻击或打击报复的意思。
首先我表明立场,如果我是team lead,我不会选择这样设计的log。我甚至不会允许这样的代码进入我的代码树。
理由 1:框架华丽,但充满了用不上的功能和几乎不可能用得到的扩展点。
在我看来这个框架中至少以下几个类和方法没有存在的意义:
Formatter
看来UP是希望将来需要的时候修改log格式。我想问的是,我们真的
需要修改log格式么?Log的主要读者是软件管理员,维护人员和开发者。也就是说
Log的读者都是计算机技能相对熟练的人,对他们来说,Log格式优美与否并不是
重点。现实世界的例子我也可以举,Windows NTP系统时间服务器w32time和活
动目录服务的log格式自NT4到现在的Win7,完全没有改变过,因为不需要。
我想UP会立即跳起来拍砖,说这个Formatter类是为了将来给别的软件使用时方便
他们自己定义格式的。——可正如我之前所说,格式不重要。另外,Log这东西并
不是程序设计的重点,我必须得说,我从未见过一个严肃的产品开发组把自己工作
正常的的Log代码换成另一个而保持原有输入格式不变,因为这没有产出效益。
Processor
按我的理解,UP在试图通过Processor多播支持多数据源和数据输出。
但我想问的是,为什么我需要把log同时输出到文件、数据库和网络上去?对程序员
而言,只要有一份准确的log就足够分析了,连续增加三份一模一样的log不会给
调试带来任何帮助,而且这样进一步加重了Log本身的开销。
另一个牢骚发生在这里:由于Processor这个公共基类的存在,Source和Sink
被强迫使用同一个Process(Entry) 方法,而偏偏Source的参数实际上不需要
Entry,所以代码里使用了一个dummyEntry。
如果大家还能回想起当初我曾和UP争论过的话题,这就是一个例子:为形式而
形式的框架会对后来的人员理解造成困难,因为当他们先看到
Log::AddProcess()而没有来得及看到Source的定义时,他们会用很长时间试
图理解为什么这个输入的Entry似乎被神秘地忽略了。
Entry const Source::Process(Entry const& dummyEntry) {
Entry entry = parser_->Parse(receiver_->Read());
return Processor::Process(entry);
}
Sink
说白了,Sink的作用仅仅是绑定Destination和Formatter。我不知道大家
会不会同意我对Formatter的评价。但如果我的评论能成立,那么Sink确实
没有存在的理由,直接用Destination就够了。
理由 2:基本功能缺失
简单地说是Entry不支持可变长参数。
从代码里我们可以看到每次调用Util::Log::Log()时程序员只能输入
一个entry,而entry实际上就是一个字符串的只读包装。可程序员
怎么输出一个整型或构体变量的值呢?是不是程序员得一次次地调
CString.format()或stringstream?如果是这样的话我估计没有什
么人会喜欢这个东西。
理由 3:基本功能设计错误。
我得说这个log类在Windows上没有什么实用性。理由可见FileDestination
类。如果照这样写法,那么这个log类在写log的时候别的进程是不
能打开log文件的,因为这个进程独占了那个文件。但对于长期运行
的系统,系统管理员往往需要定期地检查log,要是这么设计的话管
理员每次查log就得把进程关掉。这个对服务器来说是不可接受的。
可移植的做法应当是写一行开关一次文件。这也就是为什么很多程序
员不喜欢log的原因,因为确实效率不高。但没办法,这是唯一可移植
也是唯一可靠的方法。
另一个设计错误是Log::Update()。UP的代码里没有这个例子,那么
我就得问是谁负责调用Log::Update()?是程序员在Log写入一段时间
后主动调用么?如果是这样,那么请问这个Log框架能否在程序由于
crash而无法调用Update()时保证将尚未写入的信息写入数据库?如果
不能,那么我只能说这个log无法满足起码的健壮性需要。
总结陈词:
以一个天天要和各式各样的Log打交道的测试人员的眼光看,一个实用的Log
实现应该是:
a) 需要明确的功能实现,比如如何管理网络连接,数据库,文件等等。
b) 不需要复杂的扩展。
而这个Log框架正好相反:
a) 除了文件写入(带有严重bug)之外没有提供任何其他实际功能
b) 提供了远远超出预期的扩展性,而且这个扩展性还以一定程度上的
代码阅读困难为代价。
所以它仅仅是一个Design Pattern风格的完美框架展示。但很遗憾,我们
需要的从来都不是框架。
sorry,我设想的目标场景跟你不一样。我的参照物是现有市场上的log系统,比如:java的log系统或者c++的log系统,他们都有大量的扩展点,而且侵入性比较强。我只是想展示一下其实可以很少侵入的提供更强的扩展性的log体系而已:)。我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,毕竟,你可以只用一个而不用为任何没有用的付出任何代价,不是么?log是不是一模一样完全依赖于formatter,是吧。
ps:说实话,我真的没有设想到log服务器的意义和作用:)。
……因为当他们先看到
Log::AddProcess()而没有来得及看到Source的定义时,他们会用很长时间试
图理解为什么这个输入的Entry似乎被神秘地忽略了。
Entry const Source::Process(Entry const& dummyEntry) {
Entry entry = parser_->Parse(receiver_->Read());
return Processor::Process(entry);
}
再说一次Source是为log服务器而生的。
Sink
说白了,Sink的作用仅仅是绑定Destination和Formatter。我不知道大家
会不会同意我对Formatter的评价。但如果我的评论能成立,那么Sink确实
没有存在的理由,直接用Destination就够了。呵呵,或许Formatter能够把log转换为一条SQL语句?而Destination可以直接执行它?谁知道呢?并且,我这个框架设计的时候同事的反应并不是理解困难【实际上理解起来并不是太困难,不是么?你这么快就理解了:)】,而是有没有xx功能?能不能加上xx功能?
我觉得没有任何理由不能为Entry提供构造函数吧。甚至变长参数的构造函数也是可以的啊。我只是暂时没有这方面的需求而已。其实,Entry本身都应该是抽象的,才能为各种场景下的特异性提供适应性。
理由 3:基本功能设计错误。
我得说这个log类在Windows上没有什么实用性。理由可见FileDestination
类。如果照这样写法,那么这个log类在写log的时候别的进程是不
能打开log文件的,因为这个进程独占了那个文件。但对于长期运行
的系统,系统管理员往往需要定期地检查log,要是这么设计的话管
理员每次查log就得把进程关掉。这个对服务器来说是不可接受的。
或许我的那个方法不可移植,但是我在WinCE上用的时候没有你说的那个xian限制。
我得好好想想,Update?嗯,没有这个函数,最初log是一个纯粹的写入数据库的一个东西,有严格的结构。后来发现其实不实用,所以已经完全废弃了。
在特定的场景下,扩展是无用的。但是,作为一个log库的提供者,我不能假定任何场景。It’s so that。
我不知道它符合什么模板。但是,我们确实需要。
log-劣:1、对多线程来说有副作用,可能会掩盖某些竞争条件(减慢了速度造成程序不出错,但是遇到快速的机器和io还是要出问题)。
2、很多程序员不知道该输出什么不输出什么,经常看到无数不知所云的log,反而增加调试的复杂度
3、加错位置(忘了关闭,等等)可能大幅降低效率(我们分词器内部实现里面有个调试log,程序员发布到真实环境去了,查询效率下降到惊人的程度)。
4、只能事后去看,不能即使查看
首先说一句:如下内容和Raymond的原帖关系已经不大了,但我实在觉得有公开讨论的必要,如果不喜欢的同学请忽略此帖子。忧喜参半,喜是很高兴能得到正面的回应,忧是看来咱们俩的分歧不是代码细节上的。
2009/12/2 up duan fix...@gmail.com
sorry,我设想的目标场景跟你不一样。我的参照物是现有市场上的log系统,比如:java的log系统或者c++的log系统,他们都有大量的扩展点,而且侵入性比较强。我只是想展示一下其实可以很少侵入的提供更强的扩展性的log体系而已:)。我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,毕竟,你可以只用一个而不用为任何没有用的付出任何代价,不是么?log是不是一模一样完全依赖于formatter,是吧。
没有付出代价?我不同意,对你而言确实如此,但你让你的用户付出了惨重的代价:a) 效率:本来一个函数操作完成的任务现在变成了遍历链表 +两次虚函数调用。本来log就不是一个高效的操作,这一下效率更低。
b) 后来人的阅读开销——这是我反复强调的。如果一个东西没有用,它就***绝对不应该***出现在代码里。这东西会对维护和将来扩展造成持续的负面作用。
至于市场上的log系统,我不敢说自己非常熟悉Log4J,我承认它也提供了很多诸如XXXAppender的类负责把信息输出到文件、Socket等等等等。但是Log4J的每一个Appender都是一个明确定义的类,而不是个抽象的interface,所以它才是一个实用的工具。详情可参阅http://logging.apache.org/log4j/1.2/apidocs/index.html。
而你的代码里所有的各种Destination只有文件是有实现的,剩下的你打算怎么办?让用你的Log框架的人去实现?如果是我的话,我宁可直接用已有的。
再提一句:对于Log这种特定目的的应用,扩展性不重要。因为Log这种东西的定位非常特殊,它本身是产品代码不可或缺的一部分,但却对产品的功能只有***反贡献***(主要是效率),所以它必须避免在编写、阅读和部署的时候付出太大的精力,因为程序员不会有兴趣花大把的时间去考虑怎么实现一堆逻辑来适应log的接口。
作为一个Log库,其必须做到接口简单,并且功能完备。我不能要求你同意我的看法,但我确实是这么认为。
ps:说实话,我真的没有设想到log服务器的意义和作用:)。
……因为当他们先看到
Log::AddProcess()而没有来得及看到Source的定义时,他们会用很长时间试
图理解为什么这个输入的Entry似乎被神秘地忽略了。
Entry const Source::Process(Entry const& dummyEntry) {
Entry entry = parser_->Parse(receiver_->Read());
return Processor::Process(entry);
}
再说一次Source是为log服务器而生的。我的看法是如果这是一个不同的概念,那么就应该有不同的方法来表示。我不太理解你说的服务器是什么,但只要是接受Log信息的东西,根据你的框架定义,它就应当接受那个参数并处理。而你的代码行为并不是这样的。最要命的是你的代码里还没有文档,如果你不说是为了服务器考虑,我看了半天也不知道**为什么**这个dummyEntry不会被处理。我相信每一个人看代码的人都会遇到像我一样的问题。
>>> ps:说实话,我真的没有设想到log服务器的意义和作用:)。如果设计者都没有明确地理解log服务器的作用,那么我不敢信任这个设计的可行性。
Sink
说白了,Sink的作用仅仅是绑定Destination和Formatter。我不知道大家
会不会同意我对Formatter的评价。但如果我的评论能成立,那么Sink确实
没有存在的理由,直接用Destination就够了。呵呵,或许Formatter能够把log转换为一条SQL语句?而Destination可以直接执行它?谁知道呢?并且,我这个框架设计的时候同事的反应并不是理解困难【实际上理解起来并不是太困难,不是么?你这么快就理解了:)】,而是有没有xx功能?能不能加上xx功能?
也许,又是也许。为什么要让他转换?我不敢想象某个用了你的log的软件在运行时写上文档说:为了得到正确的log输出,使用该软件时请安装SQLServer服务器。这太荒谬了。
我和你对框架的理解不一样。我的观点是框架的设计者必须牢牢地控制用户场景,理解每一个扩展点的用途和可能的变化。对自己不理解的场景随便加一个接口不是负责的态度。
Sink本身理解上并不困难,而这也就是为什么能这么快断言这东西没有存在的必要性的原因。
我觉得没有任何理由不能为Entry提供构造函数吧。甚至变长参数的构造函数也是可以的啊。我只是暂时没有这方面的需求而已。其实,Entry本身都应该是抽象的,才能为各种场景下的特异性提供适应性。问题不在于能不能加入,而是在于**你现在没有实现**。如果是要做一个通用的产品,那么这东西应当能保证一个完整的基本用户场景,而不是到时候再扩展。
我妄言一句:如果你说你暂时没有这方面的需求,那么我可以立即断言这个框架还没有用在实际的生产环境里。因为我也曾经写过一个Log框架,一开始也没有提供变长参数,而第一次用到生产环境中的时候就遭到了同事的抱怨,因为他们厌倦了到处写stringstream。我不相信任何一个需要写log的程序员会没有这个要求。
理由 3:基本功能设计错误。
我得说这个log类在Windows上没有什么实用性。理由可见FileDestination
类。如果照这样写法,那么这个log类在写log的时候别的进程是不
能打开log文件的,因为这个进程独占了那个文件。但对于长期运行
的系统,系统管理员往往需要定期地检查log,要是这么设计的话管
理员每次查log就得把进程关掉。这个对服务器来说是不可接受的。我不清楚你说的“写log的时候“是指什么时候?是写的那一刹那呢?还是从打开文件到关闭文件?我不知道是不是那一刹那不能打开log文件,但是我自己可以在任何时候能打开它。不需要把进程关掉。或许我的那个方法不可移植,但是我在WinCE上用的时候没有你说的那个xian限制。
这是我说错了,我得道歉。我重新查了一下我的测试代码,出错的原因是我自己写的验证代码中忽略了那个fflush(),就是缺少了fflush()导致log信息被存在buffer里而别的进程打开文件时看不到。Windows下的自动文件锁来自CreateFile()。默认情况下在没有明确指定sharing mode时Windows确实会自动锁住这个文件,导致别的进程试图读取文件时出现拒绝访问错误,这符合我的经验。看来我这个错误印象是从这里来的。没有调查就没有发言权啊,也是给我一个教训,呵呵。我得好好想想,Update?嗯,没有这个函数,最初log是一个纯粹的写入数据库的一个东西,有严格的结构。后来发现其实不实用,所以已经完全废弃了。再次强调,既然没有这个函数,就得尽早清除。我们不能假设每一个后来人都能像你这个设计者一样知道哪些API已经过时。
在特定的场景下,扩展是无用的。但是,作为一个log库的提供者,我不能假定任何场景。It’s so that。我不知道它符合什么模板。但是,我们确实需要。以下几句话都是从你的原文摘出来的:>>> 我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,>>> ps:说实话,我真的没有设想到log服务器的意义和作用:)。>>> 作为一个log库的提供者,我不能假定任何场景。看来这就是我们两个的根本分歧。我认为定义一个软件的用户场景是设计师的责任,而不是用户的。设计师有责任去定义用户场景,甄别用户提出的需求,并选择性地说Yes或者No,否则这个软件的膨胀速度就会失控。所以架构师在处理用户要求的时候必须完整地考察并理解每一个要求的适用性和可行性,而不是不加选择地把所有可能的功能都加到框架里来。
你说“我们确实需要”,但从你之前的回复上看你并没有真的去理解你的同事提出要求的原因和可行性,只是简单地加了一个新的interface了账,而且恕我直言,在我看来这个interface加得不算高明。
我一直拿这句话提醒自己:每增加一个抽象都意味着总得有一些人必须为此增加多个业务逻辑,而业务逻辑代码永远比写一个interface困难许多。所以代码的设计者必须抵制随意增加接口的诱惑,因为“现在没有代价”不等于以后永远没有,而我不能要求将来接手我的工作的同事为我的一时兴起而承担不必要的代价。
127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET
/apache_pb.gif HTTP/1.0" 200 2326
"http://www.example.com/start.html" "Mozilla/4.08 [en]
(Win98; I ;Nav)"
我需要首先澄清一个概念,在我的工作环境中debug和诊断是没有明确界限的,
因为工作需要中我经常要控管整个网络,而不是某个产品的代码。所以有时候我
和我的同事们会不自觉地将其混为一谈。如果仅仅从上面这行log来看,我认为
这里提供的信息足以让我看到很多东西。至少如果存在这样一条信息,我就有自信
不再怀疑网络是否出出现短暂的连接不上。假定我们知道某个产品总是在Get操作
结束时输出信息,那么我至少可以肯定Get操作已经完成。
当然这些东西显然还不足以进行代码级别的debug,所以如果某个产品的设计师
告诉我他的web服务器只能输出这些东西,那么我也会告诉他这不可接受。
如果我的表述还是不够清楚,确实抱歉,因为我好像也没带字典,呵呵。
>>>
a) 效率:本来一个函数操作完成的任务现在变成了遍历链表 +两次虚函数调用。本来log就不是一个高效的操作,这一下效率更低。嗯?我不清楚你fopen,fwrite,fclose调用跟我的这个调用相比效率能有多高?
虚函数确实调用效率低,但是究竟有多低,不过就是多了一个指针间接而已。是,需要遍历链表,可是你忘了,链表的长度是1的时候的遍历似乎也不很慢吧:)
b) 后来人的阅读开销——这是我反复强调的。如果一个东西没有用,它就***绝对不应该***出现在代码里。这东西会对维护和将来扩展造成持续的负面作用。问题是什么叫有用?我觉得MFC里面的永久化机制对我完全没有用,它是不是就不应该出现在MFC框架里面?
至于市场上的log系统,我不敢说自己非常熟悉Log4J,我承认它也提供了很多诸如XXXAppender的类负责把信息输出到文件、Socket等等等等。但是Log4J的每一个Appender都是一个明确定义的类,而不是个抽象的interface,所以它才是一个实用的工具。详情可参阅http://logging.apache.org/log4j/1.2/apidocs/index.html。哦,你觉得我的Destination是抽象的接口么?
而你的代码里所有的各种Destination只有文件是有实现的,剩下的你打算怎么办?让用你的Log框架的人去实现?如果是我的话,我宁可直接用已有的。不只是文件,还有MFC的Trace,还有Console(当然,也是文件),还有MessageBox啊……,确实我的数据库和socket没有实现,但是,实现它们跟实现文件Destination没有本质的区别。 文件时fopen,socket是创建一个到log server的连接,而db是创建一个到具体数据库的连接。文件时fwrite,socket是send,db是insert。我前面说过,我不知道log server有什么用,这是我同事的要求,当然,后来他也没有搭建log server,所以socket没有实现。db也没有实现。
我不太清楚你的立场,站在log实现者的立场看,我不应该化太大精力去编写log库,站在log使用者的立场看,我的log应该不花大量的时间来适应以便使用?这两个似乎都是你的猜测吧。我觉得对于一个稍微具有规模的项目【2-4个人参见】,log基础设施的配置根本就可以忽略不计【整个工程就配置一次,而且想修改配置也就是这一个地方】,而log调用跟printf相比究竟复杂在那儿?
如果设计者都没有明确地理解log服务器的作用,那么我不敢信任这个设计的可行性。这是我们同事的要求,后来没有用。设计可行性是什么意思?我不知道实现出来算不算设计可行性的一个明确的答案?
我和你对框架的理解不一样。我的观点是框架的设计者必须牢牢地控制用户场景,理解每一个扩展点的用途和可能的变化。对自己不理解的场景随便加一个接口不是负责的态度。
嗯,你说的或许有道理,但是我的理解是如果架构不能扩展到自己不能理解的场景,那么这个架构就有问题。
问题不在于能不能加入,而是在于**你现在没有实现**。如果是要做一个通用的产品,那么这东西应当能保证一个完整的基本用户场景,而不是到时候再扩展。什么是“完整的基本用户场景”,我看来,我的用户【也就是我的同事们】能用它替代自己fopen,fprintf ,fclose就是完整的用户场景了。
On Dec 3, 11:34 am, Tinyfool <tinyf...@gmail.com> wrote:
> 调试log,我们在release版本中一般也留着,但是默认是关掉的,但是如果客户不发现问题,我们就建议他不要打开。他发现了问题,我们才要求他打开,然后复现错误,然后把log发过来我们进行分析。
>
> 另外,写mac和iphone的程序,如果崩溃了,操作系统会收集详细的调试信息,提示给用户,要不要发给开发者或者苹果。
不光是mac和iphone可以这样。Windows和linux也可以,google-breakpad就是做这些事的。
On 12月2日, 上午3时27分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 我想我们对Code coverage的定义似乎有偏差。在我看来code coverage和优雅与否毫无关系。
>
> (顺便说一句,我个人不喜欢拿"优雅"这种过于主观的概念作为评判程序好坏的标准)
>
> 我所说的code
> coverage是通过运行测试计算在本次测试中覆盖了多少产品代码。我们公司内部有一些专门的工具用来做这个,通过在编译器和函数库级别注入一些代码,得到一个专门的code
> coverage版本。只要在这个版本上运行测试就可以自动生成一些数据,记录在本次测试中有多少代码被覆盖到,一般可以精确到代码块级别。也就是说我可以知道一个if语句的then块和else块究竟哪一个被覆盖到。
>
> Linux也有gcov,不过我没怎么用过。如果有熟悉的同学不妨介绍一下。
>
> 在我的工作中,Code coverage主要的作用有两个:
>
> a) 衡量某个测试方案是否存在漏洞。
>
> 在我看来Code coverage高不等于测试的质量一定高,但Code
> Coverage低则必然说明测试质量低下。因为覆盖率低表明测试中遗漏了功能点,换句话说很多代码根本没经过测试。
>
> b) 管理回归测试。
>
> 我们公司内部有一个数据库用来保存每个测试和产品代码的对应关系,以及产品代码之间的调用关系。这样当一个新的fix被引入时,我们可以通过数据库来查询究竟有多少相关的其他产品代码可能被这个fix影响,从而确定这一次fix需要增加哪些测试。
>
> 对于个人开发而言,b) 可能已经超出我们力所能及的范围了。不过用gcov验证一下测试的质量还是有好处的,呵呵。
>
> 2009/12/1 raymond <shiqu...@gmail.com>
>
>
>
> > 说到code coverage,我之前一直觉得code coveage最多是代码优雅方面的问题(写的不好的代码当然code coverage
> > 差,bug多),难道这个跟debug还有很大关系?兄弟明说一下。
>
> > 看了一下介绍,iDNA基本是调试单机程序的,而且应该适合调试那种逻辑多,数据少的程序,而分布式调试一般处理的是逻辑不多,数据量大的程序。
>
> > On 12月1日, 上午3时58分, Fuzhou Chen <cppof...@gmail.com> wrote:
> > > 呵呵怎么着我也是把问题带跑的那个人,既然楼主都发话了我们还是回到正题吧。
>
> > 首先我得说我没有做MapReduce类型应用的经验,我现在的工作集中在Windows活动目录这一块,特征是低平均通信强度下的服务器LDAP数据库信息同步。所以我的观点基本上基于我的背景,可能和楼主期望的场景有所不同,如果有不同意见欢迎指出。
>
> > > 2009/11/30 raymond <shiqu...@gmail.com>
>
> > > > 1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
> > > > 时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
>
> > > 我的观点很简单:
>
> > > a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> > > b)
> > 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> > > + Code Coverage + Fuzzying。
> > > c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
>
> > > > 2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
> > > > 可能。。。,
>
> > > > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
>
> > 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。
>
> > 关于如何设计多机调试框架的问题,我知道的一些项目基本上还是依赖于log,而他们的设计则比我们常用的log复杂很多。我曾经听一个同事说起过某个杀毒软件公司(似乎是瑞星)的内部log框架,他们的log不是直接写在本机上的,而是每个进程有一个独立的线程来收取log,然后各个log线程会把信息发送给一个中央的服务器,由中央服务器根据时间戳把所有的log组合成一个单独的文件。那个框架用了很多buffer机制来避免log输出对功能模块造成额外的性能开销。不过我没有自己用过,效果如何难以评价。
>
> > 我自己有时候用一个山寨版的简化方案:比如每个程序还是在本机写log,但测试最后我会通过文件共享把这些log收集到单独的一台机器上,然后通过一些script分析时间戳并组合到单独的一个文件里。只要测试不要求修改系统时间(比如NTP协议的测试,这个我也做过),这个方案基本上还是可行的。
>
> > > 至于调试能够稳定重现的bug,不知道在这里的同学们有没有机会用过微软的iDNA,
> >http://blogs.msdn.com/cse/attachment/1077668.ashx
>
> > 。它可以很方便地抓取程序执行中每一个汇编指令执行时的程序瞬态的内存信息,然后存成一个dump。利用这个dump我们就可以在Windbg里虚拟地对进程的执行情况做调试,甚至能做到单步后退,算是我见过的最好的产品化的调试工具。但这个工具最大的限制是硬盘空间,因为它生成的dump文件大小会随着程序执行时间快速膨胀,所以它不适用于需要长时间运行的程序,也很难用于一些硬盘空间有限制的生产环境。
>
> > > 我不知道Linux下有没有类似的技术,有高手可以说明一下否?
On 12月2日, 上午3时53分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 这话题是越发沉重了啊。以下可能有些离题,Raymond别见怪。
>
> 毋庸讳言,写出真正能对调试有帮助的log的工作量实际上是很大的。而从我的工作经验上看,我只能很遗憾地说不写log很大程度上出于两个原因:
>
> a) 开发组把写log看作是一种纯粹的负担,而不是为了保证产品的长期可维护性必须引入的步骤。
>
> 我知道的为数不少的公司,开发组写完代码只负责函数级别的Unit
> test,剩下的功能/集成测试和维护完全靠测试组完成。很多时候测试功夫不到家,只能发现简单的bug,这时开发组凭着自己对产品的了解往往可以快速解决。既然测试组抓不出必须有log才能解决的bug,开发组就可以堂而皇之地拒绝测试组对log的需求。
>
> 造成这种问题的另一个原因是开发组不理解测试的难度所在,他们把测试当作是一种可以像添加功能一样进度可知的东西,或者把unit
> test等同于功能测试。我所知道的很多开发人员很不理解为什么即使测试通过了的产品,发布出去之后仍然能发现bug。他们喜欢说:如果出了问题就说明你们这些测试是吃干饭的。----很无奈,但他们中的很多人自己甚至不测试自己的代码。
>
> b) 有时候不写log纯粹是因为某个自以为是的老板认为这样可以不暴露商业机密----我知道这种政治问题本不该由我们操心,但人在江湖,没办法。
>
> 2009/12/1 stefan <pengli.h...@gmail.com>
>
>
>
> > 那我想请问:如果release版本没有log信息,长程序在用户那里出现问题时,程序员应该如何进行定位,难度还要让用户进行现场恢复,
> > 程序员则去取个debu版本过来吗?
> > 或者在用户那里开启调试工具,我想,这些场景似乎都不应该出现吧。
>
> > 正如你说的,当用户机器慢时没有问题(暂且认为正常),当用户机器升级后暴露了问题,如果有日志,正好可以帮助进行定位,为什么不呢?
>
> > 在这样大系统中,合理设计大log系统并不会成为系统的瓶颈吧。
>
> > On 11月30日, 下午3时05分, Tinyfool <tinyf...@gmail.com> wrote:
>
> > 没有log就会产生的竞争条件,不解决往往是很危险的。因为在某机器上log的效率下降恰好把竞争条件掩盖了,而随着用户机器的升级,更快的cpu和io下,这些竞争条件可能还会重新出现。所以,debug有log没有问题,那就给release版本也加上log来解决竞争条件是非常不负责任的。而且,对复杂的程序来说,调试log带来的性能下降是不能忽略的,这就是为什么调试log仅应该在debug的时候存在。
>
> > > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>
>
> > > > 恐怕殷兄的说法恰恰证明了只有log才能解决问题,而调试毫无办法。
>
> > 这里最大的问题是:为什么release版本就不输出log?如果release版本因为有了log而导致问题不再产生,那么这个bug报告就根本不会出现;如果release版本有了log仍然能出现,那么我们从release版本的log就能看到我们需要的信息,我们为什么还需要debug版本?
>
> > > > 2009/11/29 殷远超 <yinyuanc...@gmail.com>
>
> > 对头,我这曾经有个问题,就是release版有问题,debug版始终没有,调试无法重现。后来发现log降低效率之后,完全不产生竞争条件,对于共享内存的模型,线程/j进程安全绝对不是log能解决的。
>
> > > >> 2009/11/30 Tinyfool <tinyf...@gmail.com>
>
> > > >>> 多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
> > > >>> 当然调试器也会有类似的影响。
>
> > > > --
> > > > 《采莲》·江南
>
> > > > 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> > > > 另抄自蒜头的评论:http://www.douban.com/review/1573456/:
>
> > > > 且祭一束紫琳秋,为一段落花流水的传说
> > > > 且饮一杯青花酒,为一场几多擦肩的错过
> > > > 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> > > > 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
> > > --
> > > Tinyfool的开发日记http://www.tinydust.net/dev/
> > > 代码中国网http://www.codechina.org
> > > myTwitter:http://twitter.com/tinyfool
1. 压力测试和查找性能热点
2. 重构支持 ( 验证重构的工作是否正确 )
3. 查找 bug.
特别是逻辑复杂, 很难重复 bug, 不用这种的技术, 基本上无法解决.
On 12月4日, 上午1时31分, raymond <shiqu...@gmail.com> wrote:
Jockey is a user-space library for recording and replaying an
execution of generic GNU/Linux programs. It is a debugging tool
especially for long-running networked servers that suffer from bugs
that are difficult to reproduce.
在它的网址上有对应的源代码和论文,请参考:
On 12月2日, 上午1时52分, raymond <shiqu...@gmail.com> wrote:
> redsea兄这套方法非常切合我的想法,再问几个细节:
>
> 1. 你是把所有的重要调用过程全部记录下来,然后replay的时候,按照顺序重新调用吗?比如,之前发送了一个消息,现在也要发送一个一样的;之前
> 做了排序,现在也做一遍?如果是这样,这个replay框架是不是非常大和复杂?是不是要支持很多类似反射的东西?是不是针对每个功能点还要写支持
> replay附加操作?
>
> 2. 需要保存的东西包括:
> 平台api,输入输出文件,消息,方法参数,时间序列。保存这些元素的格式方面有什么需要注意的吗?
>
> 还有一个问题想问,我们现在的log格式多是
> 时间,级别,调用方法,代码行,线程id,二进制文件。我感觉要想达到replay的功能还差的很远,那么,在log格式的设计上,还请兄弟给点建
> 议。
>
> 能不能举个贯通的例子说明一下。十分感谢。
>
> On 12月1日, 上午10时16分, redsea <red...@gmail.com> wrote:
>
> > > > 1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
> > > > 时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
>
> > 我现在做的服务器有类似的功能, 我们管它叫作 replay, 需要保存的东西
> > 1. 所有 os platform api, 包括 文件, ioctl, 等等
> > 这就包括了, 和其他进程的通信, 现在我们用的是 tcp socket
> > 2. 未必能够重复的 c library function 例如, rand
> > 3. 所有时间相关的东西, 包括所有时间相关的系统调用, 以及所有被封装的 rdtsc 调用, 需要专门处理.
> > 这个需要专门的处理, 包括获取时间(当前时刻时间值, 时间片长度), 以及sleep
> > 4. 我们现在的程序没有, 但是如果其他程序有的话, 也需要处理的: 和其他进程进行同步(例如 mutex 等)
>
> > 另外, 程序还不能是多线程的, 因为 replay 过程中, 你无法控制多线程的调度次序.
>
> > 最麻烦的不是保存数据, 而是 程序的时间控制/主调度框架, 因为这和正常执行毕竟是不一样的, 只要有一点点区别, 由于蝴蝶效应, 后面就
> > 会完全不一样了.
>
> > 这个方法还有一个麻烦的问题在于 (大概只是对于 C/C++), linux clib 每次跑, malloc 分配的地址空间是不同的
> > (大概是堆扰乱防止容易被攻击? 但是我没有看到取消点), 某些非法访问的操作, replay 过程中, 未必会在同一个地方出现, 如果提早
> > 出现了还好, 如果晚出现了, 这次 replay 可能就不出错了. ( 这个问题我还没有深入分析, 如果是 c library 造
> > 成, 可以用替换 malloc 库的方法解决, 如果是 linux kernel 造成, 可能就需要想更深入的方法了.)
>
> > > 我的观点很简单:
>
> > > a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> > > b) 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> > > + Code Coverage + Fuzzying。
> > > c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
>
> > 确实如此.
>
> > > > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
>
> > 如果面向大众的服务器, 呵呵, 这也是不能做到的.
>
> > > 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。
>
> > 说得好 :)
> > 我也是因为好几个 log 在开发环境无法重现, 而在生产环境时不时冒出来, 才费力去开发一套 replay 机制, replay 也是
> > 挺费时间的, desktop cpu + overclock 比生产环境快不到 50%, 生产环境平均 cpu 利用率 50%, 那么生产环境
> > 跑6天出的错误, replay 环境需要跑完第二天才能到, 挺费力的一件事情.
>
> > > 关于如何设计多机调试框架的问题,我知道的一些项目基本上还是依赖于log,而他们的设计则比我们常用的log复杂很多。我曾经听一个同事说起过某个杀毒软件公司(似乎是瑞星)的内部log框架,他们的log不是直接写在本机上的,而是每个进程有一个独立的线程来收取log,然后各个log线程会把信息发送给一个中央的服务器,由中央服务器根据时间戳把所有的log组合成一个单独的文件。那个框架用了很多buffer机制来避免log输出对功能模块造成额外的性能开销。不过我没有自己用过,效果如何难以评价。
>
> > log 的性能问题, 我觉得只要不是在hotspot 处, 都不应该是问题, 如果检查到已经造成拖累了, 那么可以开发一个 log
> > process, 和主进程通过 lock free ring buffer 通信, 这样的话, 主要的开销其实只是主进程里面的格式化而已
> > ( printf or io stream).
>
> > 至于 hotspot 哪里, 代码量至少不会很大, 再review 几次 code, 去掉 log 问题也不大啊. 如果是要加上错误
> > 情况检查, 那么写一个为 cpu 分支预测考虑的检查代码, 开销应该也不会很大的. ( gcc 学习 linux kernel 的
> > likely, unlikely, vc 没有这个, 就能将常见情况写作肯定分支了).
Mumak -- Using Simulation for Large-scale Distributed System
Verification and Debugging (Hong Tang@Yahoo!)
这是一个调试hadoop的平台。
我个人没有hadoop实战经验,不乱说。在MPI上的调试,一般还是依靠LOG。先搞清楚崩溃现场是哪个节点,然后打开一个宏开关,重跑一次,但这一
次其他节点实际都不跑,只有问题节点上在真正运行程序,并输出比较详细的跟踪日志。某些时候,还得指望最基本的断点printf大法。
On 12月6日, 下午5时50分, "wangleh...@gmail.com" <wangleh...@gmail.com>
wrote: