The following sample program (eut_testcov.C) was copied to the directory (/usr/lpp/ppe.dpcl/samples/testcov) when you installed DPCL. It illustrates how you can use the DPCL system to create a simple test coverage tool that periodically reports the frequency of function calls in a particular module.
First let's look at how this DPCL test coverage tool appears from the end user's point of view, and then we will examine its source code in more detail. First of all, depending on the program arguments, you can:
eut_testcov <host name> pid <process ID>
eut_testcov <host name> path </full path/executable>
eut_testcov <host name> poe_pid <poe process ID>
eut_testcov <host name> poe_path </full path/poe> </full path/executable>
Here's a sample run of the eut_testcov program. In this sample run, the arguments specify that the eut_testcov program should start a particular POE application running as 4 processes. Since the program is designed to show the test coverage for a particular module in a particular process, the user is first prompted to select a particular process.
Enter the process number to probe (0 to 3) 0 Using process 0
Once the particular process is entered, the user is then prompted to select a module in the process.
Enter the source file name to check test coverage. type !Help to list source files prod_cons.c Enter the name of the final output file. cov_out
Once the module is selected, the DPCL test coverage tool will then display the frequency of function calls in that module at intervals of 15 seconds until the end user interrupts this by pressing <Control>-c. When the user presses <Control>-c, the program then prints the final test coverage information to a file. Here is some sample output showing the kind of information displayed. Note here that some of the output is coming from the test coverage tool, while the produce/consume messages are coming from our sample target application. For clarity, the following sample output shows the analysis-tool messages in bold.
bcreate() was successful. Using Process 25648 on host pe02.pok.ibm.com... Module prod_cons.c found... expanding bexpand() was successful. function "f2" found function "produce" found function "consume" found function "main" found function "alarm_handler" found function "consume_data" found function "domath" found function "produce_data" found *Display interval set at every 15 seconds* *Enter <ctrl>-c to exit* bstart() was successful Compute 3: checking in produce 3 0 Control 0: #nodes = 4 Control: expect to receive 1200 messages Compute 1: checking in produce 1 0 Compute 2: checking in produce 2 0 consume 3 0 produce 3 1 produce 2 1 produce 1 1 consume 2 0 produce 3 2 produce 2 2 produce 1 2 consume 1 0 produce 3 3 produce 2 3 ** Number of times each function was executed: ** f2 0 ** produce 0 ** consume 1 ** main 1 ** alarm_handler 0 ** consume_data 3 ** domath 3 ** produce_data 0 ** Test coverage = 44 % produce 1 3 consume 3 1 produce 3 4 produce 2 4 produce 1 4 consume 2 1 produce 3 5 produce 2 5 produce 1 5 produce 3 6 consume 1 1 produce 2 6 produce 1 6 produce 3 7 consume 3 2 produce 2 7 produce 1 7 <ctrl>-c ** Number of times each function was executed: ** f2 0 ** produce 0 ** consume 1 ** main 1 ** alarm_handler 0 ** consume_data 7 ** domath 7 ** produce_data 0 ** Test coverage = 44 % *Please check "/u/cywong/public/eut_testcov.res" for the final result. produce 3 8 consume 2 2 battach() was successful bdestroy() was successful
Now let's look at the source code for the DPCL test coverage
tool. First, let's take a look at the complete source, then
we'll describe its parts in more detail.
// IBM_PROLOG_BEGIN_TAG // This is an automatically generated prolog. // // // // Licensed Materials - Property of IBM // // Restricted Materials of IBM // // (C) COPYRIGHT International Business Machines Corp. 1999 // All Rights Reserved // // US Government Users Restricted Rights - Use, duplication or // disclosure restricted by GSA ADP Schedule Contract with IBM Corp. // // IBM_PROLOG_END_TAG // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /* Program: eut_testcov Program function: Periodically shows the frequency of function calls in a module Usage: eut_testcov <host name> <pid> <module name> OR eut_testcov <host name> poe_pid <poe pid> OR eut_testcov <host name> path </full path/executable> OR eut_testcov <host name> poe_path </full path/poe> </full path/executable> - specify "d" for <host name> to use default host */ // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% const int INTERVAL=15; //time interval for display information const int BUFLEN=80; //size of buffer for storing file and module name char FILEOUT[128]; // -------------------------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <string.h> #include <AisGlobal.h> #include <AisInit.h> #include <AisMainLoop.h> #include <PoeAppl.h> #include <Process.h> #include <InstPoint.h> #include <ProbeExp.h> #include <Application.h> #include <ProbeType.h> #include <ProbeHandle.h> #include <InstPoint.h> #include <SourceObj.h> #include <AisStatus.h> #include <LogSystem.h> #include <AisHandler.h> #include <iostream.h> // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Process P; PoeAppl A; struct fun_info{ char funname[256]; int pcount; }; int fun_count=0; int fun_num; int lflag=0; //Ais_main_loop() started flag fun_info *fun_arr = NULL; int num_installed = 0; char filename[BUFLEN]; void write_results(void); void ck_process(void); void print_fun_info(void); void get_Process(void); void data_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg); void stdout_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg); void stderr_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg); int sighandler(int signal); int sighandler_ALRM(int signal); // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% main(int argc, char *argv[]) { const char USAGE1[]="\nUSAGE: eut_testcov <host name> pid <pid>\n"; const char USAGE2[]="\t\tOR\n\teut_testcov <host name> poe_pid <poe pid>\n"; const char USAGE3[]="\t\tOR\n\teut_testcov <host name> path </full path/executable\n"; const char USAGE4[]="\t\tOR\n\teut_testcov <host name> poe_path </full" "path/poe> </full path/executable>\n"; const char USAGE5[]="\t*specify \"d\" for <host name> to use default host\n"; const int bufSize = 128; int hostname_length; char hostname[bufSize]; // buffer for get_host_name() if (argc < 4){ cout<<"\n**Incorrect numbers of argument" "entered**"<<USAGE1<<USAGE2<<USAGE3<<USAGE4<<USAGE5<<endl; exit(0); } char **argvv=argv+3; Ais_initialize(); Ais_add_signal(SIGINT,sighandler); Ais_add_signal(SIGALRM,sighandler_ALRM); if(strcmp(argv[1],"d")==0){ gethostname(hostname,BUFLEN); printf(" *Running on \"%s\"\n",hostname); } else strcpy(hostname,argv[1]); if (strcmp("poe_pid",argv[2]) == 0) { //POE AisStatus sts = A.binit_procs(hostname, atoi(argv[3])); if (sts.status() != ASC_success){ printf("binit_procs() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("binit_procs() was successful.\n"); sts = A.bconnect(); if (sts.status() != ASC_success){ printf("bconnect() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bconnect() was successful.\n"); } get_Process(); } sts=A.battach(); //stop the application if (sts.status() != ASC_success){ printf("battach() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("battach() was successful.\n"); } } else if (strcmp("pid",argv[2]) == 0){ //Not POE P = Process(hostname, atoi(argv[3])); AisStatus sts = P.bconnect(); if (sts.status() != ASC_success){ printf("bconnect() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bconnect() was successful.\n"); } sts=P.battach(); //stop the application if (sts.status() != ASC_success){ printf("battach() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("battach() was successful.\n"); } } else if (strcmp("path",argv[2]) == 0){ //start a new appl AisStatus sts = P.bcreate(hostname,argv[3],argvv,NULL, stdout_cb,NULL, stderr_cb,NULL); if (sts.status() != ASC_success){ printf("bcreate() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bcreate() was successful.\n"); } } else if (strcmp("poe_path",argv[2]) == 0){ AisStatus sts=A.bcreate(hostname,argv[3],argvv,NULL, stdout_cb, NULL, stderr_cb, NULL); if (sts.status() != ASC_success){ printf("bcreate() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bcreate() was successful.\n"); } get_Process(); } else { cout<<USAGE1<<USAGE2<<USAGE3<<USAGE4<<USAGE5<<endl; exit(0); } printf("Using Process %d",P.get_pid()); P.get_host_name(hostname,bufSize); printf(" on host %s...\n",hostname); SourceObj myprog = P.get_program_object(); SourceObj mymod; char bufmname[bufSize]; // buffer for module_name(..) char name[BUFLEN]; int found=0; while(found==0) { cout<<"Enter the source file name to check test coverage."<<endl; cout<<"type !Help to list source files"<<endl; cin>>filename; if(strcmp(filename,"!Help")==0) { // loop through source files to list cout<<"Source files:"<<endl; int count=myprog.child_count(); for(int x=0;x<count;x++) { mymod=myprog.child(x); mymod.module_name(name,BUFLEN); cout<<x<<" "<<name<<endl; } } else { for (int c = 0; c < myprog.child_count(); c++) { mymod = myprog.child(c); const char * modname = mymod.module_name(bufmname,bufSize); if (strcmp(modname, filename) ==0) { printf("Module %s found... expanding\n",filename); AisStatus sts = mymod.bexpand(P); if (sts.status() != ASC_success) { printf("bexpand() was not successful.. %s\n",sts.status_name()); exit(0); } else { printf("bexpand() was successful.\n"); } found=1; break; } } //} if (found == 0) cout<<"\""<<filename<<"\" not found.\n"; } } //end while cout<<"Enter the name of the final output file."<<endl; cin>>FILEOUT; ProbeExp parms[3]; parms[0]=Ais_msg_handle; parms[1]=ProbeExp("xxx"); //send dummy information to application parms[2]=ProbeExp(4); ProbeExp mysend=Ais_send.call(3,parms); SourceObj myfun; char bufdname[bufSize]; // buffer for get_demangled_name(..) #ifdef DEBUG printf("mymod.child_count() = %d\n",mymod.child_count()); #endif fun_count=mymod.child_count(); fun_arr = new fun_info[fun_count]; printf("\n"); for ( int c = 0; c < fun_count; c++) { myfun = mymod.child(c); const char * funname = myfun.get_demangled_name(bufdname,bufSize); if (funname == NULL) continue; printf("function \"%s\" found\n",funname); strcpy(fun_arr[fun_num].funname,funname); fun_arr[fun_num].pcount=0; fun_num++; InstPoint mypoint; for ( int d = 0; d < myfun.exclusive_point_count(); d++) { mypoint = myfun.exclusive_point(d); if ( mypoint.get_type() == IPT_function_entry) { #ifdef DEBUG printf(" Found function entry point.\n"); #endif ProbeHandle myph; GCBFuncType mydcb = data_cb; GCBTagType mytg = (GCBTagType) (fun_num-1); AisStatus sts = P.binstall_probe(1, &mysend, &mypoint, &mydcb, &mytg, &myph); if (sts.status() != ASC_success){ printf(" binstall_probe() was not successful.. " "%s\n",sts.status_name()); exit(0); } else { ++num_installed; #ifdef DEBUG printf(" binstall_probe() was successful\n"); #endif } sts = P.bactivate_probe(1, &myph); if (sts.status() != ASC_success){ printf(" bactivate_probe() was not successful.. %s\n",sts.status_name()); exit(0); } else { #ifdef DEBUG printf(" bactivate_probe() was successful\n"); #endif } break; } } } printf("\n*Display interval set at every %d seconds*\n",INTERVAL); printf("*Enter <CTRL>-c to exit*\n"); sleep(3); if(strcmp(argv[2],"path")==0){ AisStatus sts=P.bstart(); if (sts.status()==ASC_success){ printf("bstart() was successful\n"); } else { printf("bstart() FAILED.. %s\n",sts.status_name()); exit(0); } } if (strcmp(argv[2],"poe_path")==0) { AisStatus sts=A.bstart(); if (sts.status()==ASC_success){ printf("bstart() was successful\n"); } else { printf("bstart() FAILED.. %s\n",sts.status_name()); exit(0); } } else if(strcmp(argv[2],"poe_pid")==0){ AisStatus sts=A.bresume(); if (sts.status()==ASC_success){ printf("bresume() was successful\n"); } else { printf("bresume() FAILED.. %s\n",sts.status_name()); exit(0); } } else if(strcmp(argv[2],"pid")==0){ AisStatus sts=P.bresume(); if (sts.status()==ASC_success){ printf("bresume() was successful\n"); } else { printf("bresume() FAILED.. %s\n",sts.status_name()); exit(0); } } alarm(INTERVAL); lflag=1; Ais_main_loop(); if(strcmp(argv[2],"path")==0){ AisStatus sts=P.battach(); if (sts.status()==ASC_success){ printf("battach() was successful\n"); } else { printf("battach() FAILED.. %s\n",sts.status_name()); exit(0); } sts=P.bdestroy(); if (sts.status()==ASC_success){ printf("bdestroy() was successful\n"); } else { printf("bdestroy() FAILED.. %s\n",sts.status_name()); exit(0); } } if (strcmp(argv[2],"poe_path")==0) { AisStatus sts=A.battach(); if (sts.status()==ASC_success){ printf("battach() was successful\n"); } else { printf("battach() FAILED.. %s\n",sts.status_name()); exit(0); } sts=A.bdestroy(); if (sts.status()==ASC_success){ printf("bdestroy() was successful\n"); } else { printf("bdestroy() FAILED.. %s\n",sts.status_name()); exit(0); } } } // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% void print_fun_info(void) { int num_tested=0; if (fun_count == 0){ printf(" No function found!!\n"); } else { printf("\n** Number of times each function was executed:\n"); for (int i=0; i< num_installed; i++){ printf("** %s\t\t%d\n",fun_arr[i].funname,fun_arr[i].pcount); if (fun_arr[i].pcount >0) num_tested++; } printf("\n** Test coverage = %d %%\n",(num_tested*100)/num_installed); fflush(stdout); } } void data_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg) { Process *p = (Process *)obj; int i = (int) tag; #ifdef DEBUG printf("Task %d sent the function number %d\n", p->get_task(),i); #endif fun_arr[i].pcount++; } int sighandler(int s){ if (lflag == 1){ Ais_end_main_loop(); write_results(); } else { printf("*\"<CTRL>-c\" was entered. Exiting..\n"); exit(0); } return(0); } int sighandler_ALRM(int signal){ ck_process(); print_fun_info(); sleep(5); alarm(15); return(0); } void stdout_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg) { // callback to receive standard out from application and display char *p = (char *) msg; printf("stdout_cb: "); for (int i=0; i<sys.msg_size; i++) { printf("%c",*p); p++; } printf("\n"); fflush(stdout); } void stderr_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg) { // callback to receive standard error from application and display char *p = (char *) msg; printf("stder_cb: "); for (int i=0; i<sys.msg_size; i++) { printf("%c",*p); p++; } printf("\n"); fflush(stdout); } void get_Process(void){ char procname[256]; int procno,proccnt; proccnt = A.get_count() -1; cout << "Enter the process number to probe (0 to "<<proccnt<<")"<<endl; cin>>procname; procno=atoi(procname); cout<<"Using process "<<procno<<endl; P = A.get_process(procno); } void ck_process(void){ //check to see if the process was destroyed ConnectState *state=new ConnectState; AisStatus sts = P.query_state(state); if (sts.status() == ASC_success){ if (*state == PRC_destroyed){ cout<<"Process terminated. Exiting...\n"; write_results(); exit(0); } } } void write_results(void){ FILE *fileout=fopen(FILEOUT,"w"); int num_tested=0; fprintf(fileout,"Source file \"%s\":\n",filename); if (fun_count == 0){ fprintf(fileout," No function found!!\n"); } else { fprintf(fileout,"\nNumber of times each function was executed:\n"); for (int i=0; i< num_installed; i++){ fprintf(fileout," %s\t\t%d\n",fun_arr[i].funname,fun_arr[i].pcount); if (fun_arr[i].pcount >0) num_tested++; } fprintf(fileout," Test coverage = %d %%\n",(num_tested*100)/num_installed); } fclose(fileout); printf("*Please check \"%s\" for the final result.\n",FILEOUT); }
Now let's examine this program in greater detail. The first thing it does is initialize itself to use the DPCL system. Among other, general, initialization tasks (such as declaring variables), our program:
#include <AisGlobal.h> #include <AisInit.h> #include <AisMainLoop.h> #include <PoeAppl.h> #include <Process.h> #include <InstPoint.h> #include <ProbeExp.h> #include <Application.h> #include <ProbeType.h> #include <ProbeHandle.h> #include <InstPoint.h> #include <SourceObj.h> #include <AisStatus.h> #include <LogSystem.h> #include <AisHandler.h>
Ais_initialize();
Ais_add_signal(SIGINT,sighandler); Ais_add_signal(SIGALRM,sighandler_ALRM);
Next, our program will either connect to or create the target application
process(es) based on the arguments that the user passed to the program.
If the user wants to: | Then our analysis tool: |
---|---|
connect to a running POE application |
|
connect to a single running process |
|
start a single-process application running | calls the Process::bcreate function to create the process. The process is created in a "stopped state"; its execution is stopped before the first executable instruction. The analysis tool will later, after point probes are installed, start this process running by calling the Process::bstart function. |
start the multiple processes of a POE application running | calls the PoeAppl::bcreate function to create the processes. The processes are created in a "stopped state"; their execution is stopped before the first executable instruction. The analysis tool will later, after point probes are installed, start these processes running by calling the Application::bstart function. |
if(strcmp(argv[1],"d")==0){ gethostname(hostname,BUFLEN); printf(" *Running on \"%s\"\n",hostname); } else strcpy(hostname,argv[1]); if (strcmp("poe_pid",argv[2]) == 0) { //POE AisStatus sts = A.binit_procs(hostname, atoi(argv[3])); if (sts.status() != ASC_success){ printf("binit_procs() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("binit_procs() was successful.\n"); sts = A.bconnect(); if (sts.status() != ASC_success){ printf("bconnect() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bconnect() was successful.\n"); } get_Process(); } sts=A.battach(); //stop the application if (sts.status() != ASC_success){ printf("battach() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("battach() was successful.\n"); } } else if (strcmp("pid",argv[2]) == 0){ //Not POE P = Process(hostname, atoi(argv[3])); AisStatus sts = P.bconnect(); if (sts.status() != ASC_success){ printf("bconnect() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bconnect() was successful.\n"); } sts=P.battach(); //stop the application if (sts.status() != ASC_success){ printf("battach() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("battach() was successful.\n"); } } else if (strcmp("path",argv[2]) == 0){ //start a new appl AisStatus sts = P.bcreate(hostname,argv[3],argvv,NULL, stdout_cb,NULL, stderr_cb,NULL); if (sts.status() != ASC_success){ printf("bcreate() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bcreate() was successful.\n"); } } else if (strcmp("poe_path",argv[2]) == 0){ AisStatus sts=A.bcreate(hostname,argv[3],argvv,NULL, stdout_cb, NULL, stderr_cb, NULL); if (sts.status() != ASC_success){ printf("bcreate() was not successful... %s\n",sts.status_name()); exit(0); } else { printf("bcreate() was successful.\n"); }
If our analysis tool has connected to or created a POE application, note that, in the preceding code, it calls the get_Process program function to prompt the user to select a single process in the POE application to instrument. The program function get_Process:
void get_Process(void){ char procname[256]; int procno,proccnt; proccnt = A.get_count() -1; cout << "Enter the process number to probe (0 to "<<proccnt<<")"<<endl; cin>>procname; procno=atoi(procname); cout<<"Using process "<<procno<<endl; P = A.get_process(procno); }
From this point on, the target application is mainly concerned with a single process represented by the Process object P. The analysis tool prints the process ID and the name of the host machine running the process to standard output. It gets this information by calling the Process::get_pid and Process::get_host_name functions.
printf("Using Process %d",P.get_pid()); P.get_host_name(hostname,bufSize); printf(" on host %s...\n",hostname);
Next, our analysis tool needs to get a reference to the source structure of the selected target application process. This source object is represented as a hierarchy of source objects (SourceObj class objects). The top level source object (the root of the hierarchy) is called the "program object", and our analysis tool gets a reference to it using the Program::get_program_object function. The SourceObj object returned by the Process::get_program_object function is assigned to the SourceObj object variable myprog. An additional SourceObj object (mymod) is instantiated to represent an individual module in the source hierarchy.
SourceObj myprog = P.get_program_object(); SourceObj mymod;
Since our analysis tool is designed to show the test coverage for a particular module only, it next prompts the user to select a module (source file). The user can either enter a source file name, or type !Help and press <Enter> to list the source files for the process. To list the source files, our analysis tool uses the SourceObj::child_count function to determine the number of modules objects under the program object. Using this number to initialize a for loop, the program then assigns each module object in turn to the SourceObj object variable mymod, and identifies the module's name using the SourceObj::module_name function. If the user enters a module name, the analysis tool code uses a similar loop to assign the appropriate module-level source object to the SourceObj object mymod, and then expands the module using the SourceObj::bexpand function. The analysis tool must expand the module because, when the DPCL system connects with a target application process, it retrieves the source hierarchy only down to the module level. Expanding the module with the SourceObj::expand or, in this case, the blocking function SourceObj::bexpand enables our analysis tool to go deeper into the source hierarchy.
while(found==0) { cout<<"Enter the source file name to check test coverage."<<endl; cout<<"type !Help to list source files"<<endl; cin>>filename; if(strcmp(filename,"!Help")==0) { // loop through source files to list cout<<"Source files:"<<endl; int count=myprog.child_count(); for(int x=0;x<count;x++) { mymod=myprog.child(x); mymod.module_name(name,BUFLEN); cout<<x<<" "<<name<<endl; } } else { for (int c = 0; c < myprog.child_count(); c++) { mymod = myprog.child(c); const char * modname = mymod.module_name(bufmname,bufSize); if (strcmp(modname, filename) ==0) { printf("Module %s found... expanding\n",filename); AisStatus sts = mymod.bexpand(P); if (sts.status() != ASC_success) { printf("bexpand() was not successful.. %s\n",sts.status_name()); exit(0); } else { printf("bexpand() was successful.\n"); } found=1; break; } } //} if (found == 0) cout<<"\""<<filename<<"\" not found.\n"; } } //end while
Now that is has a particular module to work with, our analysis tool can get to its real work -- instrumenting the target application to periodically show the frequency of function calls in the module. Basically, our analysis tool will do this by installing a point probe at the function entry point of each function in the module. Each time execution enters a function the probe will execute, sending a message back to the analysis tool. The probe's data callback will process these incoming messages, keeping track of how many times each of the functions was called.
At 15-second intervals, a SIGALRM signal handler will take coverage information collected by the probe's data callback routine and will print it to standard output. The SIGALRM signal was, as you may recall, one of the signals that the analysis tool added (using the Ais_add_signal function) to the list of signals managed by the DPCL system. When the SIGALRM signal is received, the DPCL system will call the handler specified by the analysis tool when it called the Ais_add_signal function. The other signal that the analysis tool added to the list of those managed by the DPCL system was the SIGINT signal. When the user presses <Control>-c, the DPCL system will detect the signal and will call another signal handler in the analysis tool. This one will print a final report of the information collected by the probe's data callback routine.
That's basically how our analysis tool will instrument the target application; now let's look at the specifics. First of all, the analysis tool needs to create the probe expression that, when installed as a point probe at the various function entry points, will send a message back to the analysis tool. To send data back to the analysis tool, DPCL provides a predefined probe expression (Ais_send). Ais_send is a probe expression representation of a function for sending data back to the analysis tool. The function represented by the Ais_send probe expression takes three parameters -- a message handle for managing where the message is sent, the address of the data to send, and the size of data being sent. In our case, the contents of the message is not important since a tag value passed to the data callback will track which function was called. Since the content of the message is not important, our probe will send the string "xxx". If we were able to hand code this function call into our target application, it would look like this:
Ais_send(Ais_msg_handle, "xxx", 4);
Using the ProbeExp class in DPCL, however, we have to use a slightly different approach to accomplish the same thing. That is because Ais_send is a probe expression representation of the actual function, and each parameter to the function also needs to be a probe expression. Then, all these individual probe expressions need to be combined into a single probe expression that represents the function call with parameters.
First our analysis tool needs to create an array of probe expressions, each representing one of the parameters to the Ais_send function. Note in the following code that Ais_msg_handle is another predefined probe expression supplied by DPCL. It is specifically designed for the Ais_send function for managing where the message is sent.
ProbeExp parms[3]; parms[0]=Ais_msg_handle; parms[1]=ProbeExp("xxx"); //send dummy information to application parms[2]=ProbeExp(4);
Next the analysis tool can create the probe expression that calls the Ais_send function using the three parameters defined in the parms array.
ProbeExp mysend=Ais_send.call(3,parms);
Now that the analysis tool has created the probe expression, it can install it as point probes at the function entry point for each function within the selected module. To do this, our analysis tool uses a series of nested loops as shown below. First it dives one level deeper into the module's source hierarchy to identify the function-level SourceObj objects within the module-level SourceObj object mymod. It does this by initializing the first for loop to the number of child SourceObj objects in the SourceObj object mymod. To get the number of child SourceObj objects in mymod, the analysis tool calls the SourceObj::child_count function. For each child SourceObj object in mymod, the analysis tool then uses the Source::get_demangled_name function to determine if the SourceObj object represents a function in the source hierarchy. If the SourceObj object does not represent a function, the SourceObj::get_demangled_name function will return 0, and the for loop will ignore this child SourceObj object and continue with its next iteration. If the SourceObj object does represent a function, it uses a for loop to identify the instrumentation point representing the function entry point. The analysis tool uses the SourceObj::exclusive_point_count function to identify the number of instrumentation points in the function-level SourceObj object. It then uses the SourceObj::exclusive_point function to get a reference to a particular instrumentation point for the current iteration of the loop and uses the InstPoint::get_type reference to determine if the instrumentation point represents the function entry site. (The IPT_function_entry enumeration constant of the InstPtType enumeration type indicates that the point is the function entry site.) When the analysis tool identifies a function entry site, it installs and activates its probe expression as a point probe at that instrumentation point using the Process::install_probe and Process::activate_probe functions.
SourceObj myfun; char bufdname[bufSize]; // buffer for get_demangled_name(..) #ifdef DEBUG printf("mymod.child_count() = %d\n",mymod.child_count()); #endif fun_count=mymod.child_count(); fun_arr = new fun_info[fun_count]; printf("\n"); for ( int c = 0; c < fun_count; c++) { myfun = mymod.child(c); const char * funname = myfun.get_demangled_name(bufdname,bufSize); if (funname == NULL) continue; printf("function \"%s\" found\n",funname); strcpy(fun_arr[fun_num].funname,funname); fun_arr[fun_num].pcount=0; fun_num++; InstPoint mypoint; for ( int d = 0; d < myfun.exclusive_point_count(); d++) { mypoint = myfun.exclusive_point(d); if ( mypoint.get_type() == IPT_function_entry) { #ifdef DEBUG printf(" Found function entry point.\n"); #endif ProbeHandle myph; GCBFuncType mydcb = data_cb; GCBTagType mytg = (GCBTagType) (fun_num-1); AisStatus sts = P.binstall_probe(1, &mysend, &mypoint, &mydcb, &mytg, &myph); if (sts.status() != ASC_success){ printf(" binstall_probe() was not successful.. " "%s\n",sts.status_name()); exit(0); } else { ++num_installed; #ifdef DEBUG printf(" binstall_probe() was successful\n"); #endif } sts = P.bactivate_probe(1, &myph); if (sts.status() != ASC_success){ printf(" bactivate_probe() was not successful.. %s\n",sts.status_name()); exit(0); } else { #ifdef DEBUG printf(" bactivate_probe() was successful\n"); #endif } break; } } }
In the preceding example, consider for a moment the fact that this probe expression is potentially going to be installed and activated at multiple function entry points within this same process. When execution reaches the activated probe, it will send its message back to the analysis tool where its data callback routine is triggered. Here the same data callback routine will be triggered for all the installed point probes. The data callback needs some way of knowing which function entry point has been reached. To identify the function that triggers the data callback, our analysis tool code uses the data callback's tag and the program variable fun_num. The data callback can then use this tag value to identify the function that was called. Here's the data callback code; it uses the array fun_arr to keep track of the number of times each function was called.
void data_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg) { Process *p = (Process *)obj; int i = (int) tag; #ifdef DEBUG printf("Task %d sent the function number %d\n", p->get_task(),i); #endif fun_arr[i].pcount++; }
Getting back to the main routine, we see that the analysis tool has a few final actions to make before entering the DPCL main loop (using the Ais_main_loop function) to process events asynchronously. It instructs the end user to press <Control>-c to exit, and, if it created one or more processes (using the Process::create or PoeAppl::create function), it now starts them (using the Process::start or Application::start function). It also schedules a SIGALRM signal to be delivered to the analysis tool process after 15 seconds.
printf("\n*Display interval set at every %d seconds*\n",INTERVAL); printf("*Enter <CTRL>-c to exit*\n"); sleep(3); if(strcmp(argv[2],"path")==0){ AisStatus sts=P.bstart(); if (sts.status()==ASC_success){ printf("bstart() was successful\n"); } else { printf("bstart() FAILED.. %s\n",sts.status_name()); exit(0); } } if (strcmp(argv[2],"poe_path")==0) { AisStatus sts=A.bstart(); if (sts.status()==ASC_success){ printf("bstart() was successful\n"); } else { printf("bstart() FAILED.. %s\n",sts.status_name()); exit(0); } } else if(strcmp(argv[2],"poe_pid")==0){ AisStatus sts=A.bresume(); if (sts.status()==ASC_success){ printf("bresume() was successful\n"); } else { printf("bresume() FAILED.. %s\n",sts.status_name()); exit(0); } } else if(strcmp(argv[2],"pid")==0){ AisStatus sts=P.bresume(); if (sts.status()==ASC_success){ printf("bresume() was successful\n"); } else { printf("bresume() FAILED.. %s\n",sts.status_name()); exit(0); } } alarm(INTERVAL); lflag=1; Ais_main_loop();
Once in the Ais_main_loop, the analysis tool process will respond to events asynchronously. As the target process runs and the installed probes execute, they will send data back to the analysis tool to trigger the data callback. Each message will indicate that a function was called, and, as already shown, the callback will use its tag value and an array to keep track of this information. After 15 seconds, the SIGALRM signal is delivered to the analysis tool process; it is detected by the DPCL system which calls the following signal handler (sighandler_ALRM) as specified by the analysis tool when it earlier called the Ais_add_signal function. The signal handler calls the program functions ck_process and print_fun_info. The program function ck_process checks to see if the target application process is still running. The program function print_fun_info prints the coverage information collected by the data callback, and schedules another SIGALRM signal to be delivered to the analysis tool process after another 15 seconds. Here's the signal handler followed by the functions that it calls.
int sighandler_ALRM(int signal){ ck_process(); print_fun_info(); sleep(5); alarm(15); return(0); } void ck_process(void){ //check to see if the process was destroyed ConnectState *state=new ConnectState; AisStatus sts = P.query_state(state); if (sts.status() == ASC_success){ if (*state == PRC_destroyed){ cout<<"Process terminated. Exiting...\n"; write_results(); exit(0); } } } void print_fun_info(void) { int num_tested=0; if (fun_count == 0){ printf(" No function found!!\n"); } else { printf("\n** Number of times each function was executed:\n"); for (int i=0; i< num_installed; i++){ printf("** %s\t\t%d\n",fun_arr[i].funname,fun_arr[i].pcount); if (fun_arr[i].pcount >0) num_tested++; } printf("\n** Test coverage = %d %%\n",(num_tested*100)/num_installed); fflush(stdout); } }
The preceding signal handler and function will be called at 15 second intervals in order to print the coverage information collected by the data callback to standard output. If the end user presses <Control>-c, the DPCL system detects the SIGINT signal which, like the SIGALRM signal, has been added to the list of signals it monitors. In the case of the SIGINT signal, the DPCL system calls the analysis tool's signal handler sighandler. This signal handler breaks the analysis tool out of the DPCL main event loop (using the Ais_end_main_loop function), and writes the final coverage information to a file by calling the program function write_results.
int sighandler(int s){ if (lflag == 1){ Ais_end_main_loop(); write_results(); } else { printf("*\"<CTRL>-c\" was entered. Exiting..\n"); exit(0); } return(0); } void write_results(void){ FILE *fileout=fopen(FILEOUT,"w"); int num_tested=0; fprintf(fileout,"Source file \"%s\":\n",filename); if (fun_count == 0){ fprintf(fileout," No function found!!\n"); } else { fprintf(fileout,"\nNumber of times each function was executed:\n"); for (int i=0; i< num_installed; i++){ fprintf(fileout," %s\t\t%d\n",fun_arr[i].funname,fun_arr[i].pcount); if (fun_arr[i].pcount >0) num_tested++; } fprintf(fileout," Test coverage = %d %%\n",(num_tested*100)/num_installed); } fclose(fileout); printf("*Please check \"%s\" for the final result.\n",FILEOUT); }
Since the preceding signal handler breaks the analysis tool out of the DPCL main loop, execution of the main routine can now continue past its call to the Ais_main_loop function. The analysis tool will then, since the end user has finished collecting the coverage information, attach to and kill any processes that it created and started. Processes that it merely connected to are allowed to continue running. To kill the single process or multiple processes of the POE application that it started, the analysis tool uses either the Process::battach and Process::bdestroy functions or the Application::battach and Application::bdestroy functions.
if(strcmp(argv[2],"path")==0){ AisStatus sts=P.battach(); if (sts.status()==ASC_success){ printf("battach() was successful\n"); } else { printf("battach() FAILED.. %s\n",sts.status_name()); exit(0); } sts=P.bdestroy(); if (sts.status()==ASC_success){ printf("bdestroy() was successful\n"); } else { printf("bdestroy() FAILED.. %s\n",sts.status_name()); exit(0); } } if (strcmp(argv[2],"poe_path")==0) { AisStatus sts=A.battach(); if (sts.status()==ASC_success){ printf("battach() was successful\n"); } else { printf("battach() FAILED.. %s\n",sts.status_name()); exit(0); } sts=A.bdestroy(); if (sts.status()==ASC_success){ printf("bdestroy() was successful\n"); } else { printf("bdestroy() FAILED.. %s\n",sts.status_name()); exit(0); } } }