📄 memsnapshot.cpp
字号:
/*********************************************************************************** * filename : MemSnapShot.cpp * author : mjguan * date : 2003/04/22 * description : receive message( data ) from the message queue, and analysis it. * * 1. 使用curses库写屏幕后,应该调用refresh()来刷新。 ***********************************************************************************/#include <curses.h>#include <unistd.h>#include <errno.h>#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <sys/types.h>#include <sys/msg.h>#include <pwd.h>#include <assert.h>#include <map>#include <list>using namespace std;#define SINGLE_NEW 0x00 // indicate alloc memory with new type#define ARRAY_NEW 0x01 // indicate alloc memory with new[] type#define SINGLE_DELETE 0x02 // indicate free memory with delete type#define ARRAY_DELETE 0x03 // indicate free memory with delete[] type#define FILENAME_LENGTH 32 // the filename length#define MEMORY_INFO 0X12345678 // indicate the message type on the message queuetypedef struct{ char Filename[ FILENAME_LENGTH ]; // new所在的源程序文件名 unsigned long LineNum; // new在源文件中的行号 size_t AllocSize; // 分配的内存大小 int OperationType; void * pBuffer; // 分配后得到的内存指针 short errCode; // 0 - 没有释放, 1 - delete了new[]分配的内存}MemOperation;typedef struct { int Type; // message type, in this module must be MEMORY_INFO MemOperation Data; // content of memory operation} MsgBuffer;typedef struct{ char Filename[ FILENAME_LENGTH ]; unsigned long LineNum; void *MemoryPtr; int OperationType; unsigned long TotalSize;} StatResult;int nMsgQueue; // message queue idint interval = 1;pid_t pid = 0;map<void*, StatResult> mapMemory;list<StatResult> listMemory;pthread_mutex_t mutexMap;extern int errno;pthread_t tid1, tid2, tid3;unsigned long int totalLeak = 0;WINDOW *win; void *AnalyseMessage( void * );void *DisplayStatResult( void * );void *MonitorKeyboard( void * );void initial();void popWin( char *str );void DecodeErr( int ErrNum );intmain( int argc, char *argv[] ){ key_t key; int flags, counter = 0, ch; char msgpath[ 64 ], str[ 16 ], *p; struct passwd *pwd; bool bCorrect = false; struct msqid_ds msgInfo; MsgBuffer ReceiveMsg; while( 1 ) { if ( -1 == (ch = getopt( argc, argv, "pt" ) ) ) break; switch( ch ) { case 'p': p = argv[ optind ]; // 判断输入的PID的长度的合法性 if ( strlen( p ) > 11 ) { printf( "你输入的PID数值太大了, 请检查后再试\n" ); exit( 1 ); } while ( *p ) { if ( *p < '0' || *p > '9' ) { printf( "你输入的PID含有非法字符, 请检查后再试\n" ); exit( 1 ); } p++; } pid = atol( argv[ optind ] ); bCorrect = true; break; case 't': p = argv[ optind ]; // 判断输入的PID的长度的合法性 if ( strlen( p ) > 3 ) { printf( "你输入的时间间隔数值太大了, 请检查后再试\n" ); exit( 1 ); } while ( *p ) { if ( *p < '0' || *p > '9' ) { printf( "你输入的时间间隔含有非法字符, 请检查后再试\n" ); exit( 1 ); } p++; } interval = atol( argv[ optind ] ); if ( interval < 1 ) interval = 1; if ( interval > 30 ) interval = 30; break; default: break; } } if ( false == bCorrect ) printf( "usage: MemSnapShot -p pid [ -t interval ]\n" ), exit( 1 ); pwd = getpwuid( getuid() ); sprintf( msgpath, "%s/.MemMsgQueue%d", pwd->pw_dir, pid ); // 确保m_szMsgPath表示的文件存在, 否则计算创建消息队列成功 // 其key值也将是0XFFFFFFFF(-1)。 // 如果文件不存在则创建, 创建不成功则尝试10次 do { // Open for reading and appending (writing at end of file). The // file is created if it does not exist FILE *fp = fopen( msgpath, "r" ); if ( fp ) break; } while( counter++ < 10 ); if ( counter == 10 ) exit( 0 ); key = ftok( msgpath, 'a' ); nMsgQueue = msgget( key, 0 ); if( nMsgQueue == -1 ) // the message queue exist then delete it { switch( errno ) { case EIDRM: printf( "消息队列已经被删除\n" ); break; case EACCES: printf( "消息队列存在, 但是你没有访问权限\n" ); break; default: printf( "错误, 可能的原因:\n" "1. 输入的进程(ID:%d)不存在\n" "2. 输入的进程(ID:%d)中没有含有消息队列\n" "3. 你与运行进程(ID:%d)的用户不是同一个人\n", pid, pid, pid ); break; } exit( 1 ); } initial(); // 清空当前消息队列中 if ( 0 != msgctl( nMsgQueue, IPC_STAT, &msgInfo ) ) { printf( "不能读取消息队列的属性" ); exit( 1 ); } for ( counter = 0; counter < msgInfo.msg_qnum; counter++ ) msgrcv( nMsgQueue, &ReceiveMsg, sizeof( ReceiveMsg.Data ), MEMORY_INFO, IPC_NOWAIT ); // 启动两个线程来监控内存的变化 pthread_mutex_init( &mutexMap, NULL ); // 从消息队列中读取消息并进行分析 pthread_create( &tid1, NULL, AnalyseMessage, NULL ); // 显示分析的结果 pthread_create( &tid2, NULL, DisplayStatResult, NULL ); // 监控键盘输入 pthread_create( &tid3, NULL, MonitorKeyboard, NULL ); pthread_join( tid1, NULL ); pthread_join( tid2, NULL ); pthread_join( tid3, NULL ); endwin();}void*AnalyseMessage( void *arg ){ MsgBuffer ReceiveMsg; while( 1 ) { // get the message from the message queue int reval = msgrcv( nMsgQueue, &ReceiveMsg, sizeof( ReceiveMsg.Data ), MEMORY_INFO, 0 ); if ( reval != -1 ) { map<void *, StatResult>::iterator record; record = mapMemory.find( ReceiveMsg.Data.pBuffer ); if ( record != mapMemory.end() ) // find it in the map { pthread_mutex_lock( &mutexMap ); if ( ARRAY_NEW == ReceiveMsg.Data.OperationType || SINGLE_NEW == ReceiveMsg.Data.OperationType ) { // 这个情况相当于使用new/new[]后, 没有删除 // 又分配了一块内存. // 新分配的内存的指针应该与以前的指针不相同的 // 如果相同则表示有一个delete消息被丢失了或传送的 // 消息有误 totalLeak += ReceiveMsg.Data.AllocSize; record->second.TotalSize += ReceiveMsg.Data.AllocSize; record->second.OperationType = ReceiveMsg.Data.OperationType; // 这个应该是不会出现的,如果出现了应该仔细地分析原因 // 这个情况在被测进程使用了fork创建子进程时会出现的 char prompt[ 128 ]; sprintf( prompt, "致命错误(%s:%d), 请仔细检查你的代码", __FILE__, __LINE__ ); pthread_cancel( tid2 ); pthread_cancel( tid3 ); popWin( prompt ); endwin(); exit( 1 ); } else if ( SINGLE_DELETE == ReceiveMsg.Data.OperationType || ARRAY_DELETE == ReceiveMsg.Data.OperationType ) { /**************仔细检查以下这段代码**************/ // alloc with new[] but free with delete, but not delete[] if ( SINGLE_DELETE == ReceiveMsg.Data.OperationType && ARRAY_NEW == record->second.OperationType ) { list<StatResult>::iterator ite; for ( ite = listMemory.begin(); ite != listMemory.end(); ++ite ) { if ( 0 == strcasecmp( ite->Filename, record->second.Filename ) && ite->LineNum == record->second.LineNum ) { ite->TotalSize += record->second.TotalSize; break; } } if ( ite == listMemory.end() ) listMemory.push_back( record->second ); } else totalLeak -= record->second.TotalSize; mapMemory.erase( record ); /**************仔细检查以上这段代码**************/ } else // unknown memory operation, ignore now. { char prompt[ 128 ]; sprintf( prompt, "致命错误(%s:%d), 请仔细检查你的代码", __FILE__, __LINE__ ); pthread_cancel( tid2 ); pthread_cancel( tid3 ); popWin( prompt ); endwin(); exit( 1 ); } pthread_mutex_unlock( &mutexMap ); } else { if ( SINGLE_NEW == ReceiveMsg.Data.OperationType || ARRAY_NEW == ReceiveMsg.Data.OperationType ) { StatResult result; totalLeak += ReceiveMsg.Data.AllocSize; strncpy( result.Filename, ReceiveMsg.Data.Filename, FILENAME_LENGTH - 1 ); result.Filename[ FILENAME_LENGTH - 1 ]= '\0'; result.LineNum = ReceiveMsg.Data.LineNum; result.MemoryPtr = ReceiveMsg.Data.pBuffer; result.OperationType = ReceiveMsg.Data.OperationType; result.TotalSize = ReceiveMsg.Data.AllocSize; pair<void *, StatResult> value( ReceiveMsg.Data.pBuffer, result ); mapMemory.insert( value ); } else { } } } else { if (( EIDRM == errno )||(EINVAL == errno)) // the message queue has removed, exit the thread { char *prompt = "消息队列已经被删除, 退出内存快照."; pthread_cancel( tid2 ); pthread_cancel( tid3 ); popWin( prompt ); endwin(); break; } } } return NULL;}void DecodeErr( int ErrNum ){ switch( ErrNum ) { case EAGAIN: printf( "---EAGAIN---\n" ); break; case EACCES: printf( "---EACCES---\n" ); break; case EFAULT: printf( "---EFAULT---\n" ); break; case EIDRM: printf( "---EIDRM---\n" ); break; case EINTR: printf( "---EINTR---\n" ); break; case EINVAL: printf( "---EINVAL---\n" ); break; case ENOMEM: printf( "---ENOMEM---\n" ); break; default: printf( "---Undefined---\n" ); break; }}/* ############################################################################ 显示内存泄漏的结果, 目前是一秒钟显示一次. 目前的DEBUG: . 屏幕刷新不对 ############################################################################*/void *DisplayStatResult( void *arg ){ char content[128]; int line = 0; unsigned long total; list<StatResult> temp; static bool firstFlag = true; temp.clear(); while( true ) { pthread_mutex_lock( &mutexMap ); map<void*, StatResult>::iterator record1; list<StatResult>::iterator record2; // 统计mapMemory中的泄漏记录 // 注意这里是++record1而不是record1++ for ( record1 = mapMemory.begin(); record1 != mapMemory.end(); ++record1 ) { list<StatResult>::iterator ite; for ( ite = temp.begin(); ite != temp.end(); ++ite ) { if ( 0 == strcasecmp( ite->Filename, record1->second.Filename ) && ite->LineNum == record1->second.LineNum ) { ite->TotalSize += record1->second.TotalSize; break; } } if ( ite == temp.end() ) { StatResult tmpRecord; memcpy( &tmpRecord, &(record1->second), sizeof( tmpRecord ) ); // 不能写成push_back( record1->second ) temp.push_back( tmpRecord ); } } // 统计listMemory中的泄漏记录 // 注意这里是++record2而不是record2++ for ( record2 = listMemory.begin(); record2 != listMemory.end(); ++record2 ) { list<StatResult>::iterator ite; // 同样这里是++ite而不是ite++ for ( ite = temp.begin(); ite != temp.end(); ++ite ) { if ( 0 == strcasecmp( ite->Filename, record2->Filename ) && ite->LineNum == record2->LineNum ) { ite->TotalSize += record2->TotalSize; break; } } if ( ite == temp.end() ) { // 注意这里不能直接写成temp.push_back( *record2 ); StatResult tmpRecord; memcpy( &tmpRecord, &(*record2), sizeof( tmpRecord ) ); temp.push_back( tmpRecord ); } } if ( 0 != totalLeak ) { erase(); // clear the screen mvprintw( line++, 0, "被检测的进程是 ID = %d, 目前 %d 秒进行一次统计", pid, interval ); mvprintw( line++, 0, "" ); mvprintw( line++, 0, "" ); attrset( A_REVERSE ); mvprintw( line++, 0, "%-24s%-12s%-22s%-8s\n", "所在文件", "行号", "分配内存数量(字节)", "所占比例" ); attrset( A_NORMAL ); refresh(); // 注意这里也不要写成record2++ for ( record2 = temp.begin(); record2 != temp.end(); ++record2 ) { mvprintw( line++, 0, "%-24s%-12d%-22d%2.2f%%", record2->Filename, record2->LineNum, record2->TotalSize, ( double ) record2->TotalSize * 100 / totalLeak ); } mvprintw( line++, 0, ""); mvprintw( line++, 0, "内存分配统计, 共 %d 字节(%0.1fKB = %0.1fMB)", totalLeak, ( double )totalLeak / 1024, ( double ) totalLeak / 1024 / 1024 ); refresh(); // output to screen line = 0; } else { } temp.clear(); pthread_mutex_unlock( &mutexMap ); sleep( interval ); } return NULL;}void *MonitorKeyboard( void *arg ){ int ch; for(;;) { ch = getch(); switch( ch ) { case 'q': case 'Q': endwin(); exit( 0 ); case 'i': case 'I': if ( interval >= 30 ) interval = 30; else interval++; break; case 'd': case 'D': if ( interval <= 1 ) interval = 1; else interval--; break; default: break; } }}voidinitial(){ initscr(); cbreak(); noecho(); nonl(); mvprintw( 0, 0, "被检测的进程是 ID = %d, 目前 %d 秒进行一次统计", pid, interval ); attrset( A_REVERSE ); mvprintw( 3, 0, "%-24s%-12s%-22s%-8s\n", "所在文件", "行号", "分配内存数量(字节)", "所占比例" ); attrset( A_NORMAL ); refresh();}voidpopWin( char *str ){ char *prompt = "按任何键退出"; WINDOW *popWin = newwin( 4, 70, LINES/2 - 3, COLS / 2 - 35 ); box( popWin, '#' , '#' ); if ( str ) mvwaddstr( popWin, 1, ( 70 - strlen( str ) ) / 2, str ); mvwaddstr( popWin, 2, ( 70 - strlen( prompt ) ) / 2, prompt ); touchwin( popWin ); wrefresh( popWin ); getch(); touchwin( stdscr ); refresh();}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -