/**************************************************************** * * * Copyright 2001, 2012 Fidelity Information Services, Inc * * * * This source code contains the intellectual property * * of its copyright holder(s), and is made available * * under a license. If you do not know the terms of * * the license, please stop and do not read further. * * * ****************************************************************/ #include "mdef.h" #if defined(UNIX) #include "gtm_stdio.h" #include #include "gtm_fcntl.h" #include #include "gtm_stat.h" #include "gtm_unistd.h" #include "min_max.h" #include "gtm_time.h" #elif defined(VMS) #include "gtm_stdlib.h" #include #include #include #endif #include "gtm_ctype.h" #include "gtm_string.h" #include "error.h" #include #include "stack_frame.h" #include "subscript.h" #include "svnames.h" #include "mprof.h" #include "outofband.h" #include "op.h" #include "lv_val.h" /* needed for "callg.h" */ #include "callg.h" #include "gtmmsg.h" #include "str2gvargs.h" GBLREF boolean_t is_tracing_on; GBLREF stack_frame *frame_pointer; GBLREF mval dollar_job; GBLREF uint4 process_id; GBLREF int * volatile var_on_cstack_ptr; /* volatile so that nothing gets optimized out */ GBLREF int4 gtm_trigger_depth; STATICDEF boolean_t save_to_gbl = TRUE; /* indicates whether profiling info is to be saved to db */ STATICDEF boolean_t mdb_ch_set; /* indicates whether we can rely on mdb_condition_handler and issue * an rts_error */ STATICDEF gtm_uint64_t child_system, child_user; /* store system and user CPU time for child processes */ STATICDEF gtm_uint64_t process_system, process_user; /* store system and user CPU time for current process */ STATICDEF mstr mprof_mstr; /* area to hold global and subscripts */ STATICDEF boolean_t use_realtime_flag = FALSE; /* indicates whether clock_gettime is unable to use CLOCK_MONOTONIC * flag and so should use CLOCK_REALTIME instead */ LITDEF MIDENT_CONST(above_routine, "*above*"); #define CHILDREN_TIME "*CHILDREN" /* label to store CPU time for child processes */ #define PROCESS_TIME "*RUN" /* label to store CPU time for current process */ #define MPROF_NULL_LABEL "^" #define MPROF_FOR_LOOP "FOR_LOOP" /* on VMS we do not record the child processes' time */ #ifdef UNIX # define TIMES times_usec # define CHILDREN_TIMES children_times_usec # define MPROF_RTS_ERROR(x) rts_error x #elif defined(VMS) # define TIMES get_cputime # define MPROF_RTS_ERROR(x) \ { \ if (mdb_ch_set) \ rts_error x; \ else \ { \ gtm_putmsg x; \ exit(EXIT_FAILURE); \ } \ } #endif #define UPDATE_TIME(x) x->e.usr_time += ((TREF(mprof_ptr))->tcurr.tms_utime - (TREF(mprof_ptr))->tprev.tms_utime); \ x->e.sys_time += ((TREF(mprof_ptr))->tcurr.tms_stime - (TREF(mprof_ptr))->tprev.tms_stime); \ x->e.elp_time += ((TREF(mprof_ptr))->tcurr.tms_etime - (TREF(mprof_ptr))->tprev.tms_etime); #define RTS_ERROR_VIEWNOTFOUND(x) MPROF_RTS_ERROR((VARLSTCNT(8) ERR_VIEWNOTFOUND, 2, gvn->str.len, gvn->str.addr, \ ERR_TEXT, 2, RTS_ERROR_STRING(x))); /* do the MPROF initialization */ #define INIT_PROF_FP \ TREF(prof_fp) = mprof_stack_push(); \ (TREF(prof_fp))->prev = NULL; \ (TREF(prof_fp))->curr_node = NULL; \ (TREF(prof_fp))->start.tms_stime = 0; \ (TREF(prof_fp))->start.tms_utime = 0; \ (TREF(prof_fp))->start.tms_etime = 0; \ (TREF(prof_fp))->carryover.tms_stime = 0; \ (TREF(prof_fp))->carryover.tms_utime = 0; \ (TREF(prof_fp))->carryover.tms_etime = 0; \ (TREF(prof_fp))->dummy_stack_count = 0; \ (TREF(prof_fp))->rout_name = NULL; \ (TREF(prof_fp))->label_name = NULL; /* monotonic flag for clock_gettime() is defined differently on every platform */ #ifndef CLOCK_MONOTONIC # ifdef __sparc # define CLOCK_MONOTONIC CLOCK_HIGHRES # else # define CLOCK_MONOTONIC CLOCK_REALTIME # endif #endif error_def(ERR_MAXNRSUBSCRIPTS); error_def(ERR_MAXTRACELEVEL); error_def(ERR_NOTGBL); error_def(ERR_STRUNXEOR); error_def(ERR_SYSCALL); error_def(ERR_TEXT); error_def(ERR_TRACINGON); error_def(ERR_VIEWNOTFOUND); #ifdef UNIX STATICFNDEF void times_usec(ext_tms *curr) { int res; struct rusage usage; struct timespec elp_time; res = getrusage(RUSAGE_SELF, &usage); if (res == -1) MPROF_RTS_ERROR((VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("getrusage"), CALLFROM, errno)); curr->tms_utime = (usage.ru_utime.tv_sec * (gtm_uint64_t)1000000) + usage.ru_utime.tv_usec; curr->tms_stime = (usage.ru_stime.tv_sec * (gtm_uint64_t)1000000) + usage.ru_stime.tv_usec; /* also start recording the elapsed time */ while (TRUE) { res = clock_gettime(use_realtime_flag ? CLOCK_REALTIME : CLOCK_MONOTONIC, &elp_time); if (res == -1) { if ((EINVAL == errno) && !use_realtime_flag) { use_realtime_flag = TRUE; continue; } else MPROF_RTS_ERROR((VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("clock_gettime"), CALLFROM, errno)); } break; } curr->tms_etime = (elp_time.tv_sec * (gtm_uint64_t)1000000) + (elp_time.tv_nsec / 1000); return; } STATICFNDEF void child_times_usec(void) { int res; struct rusage usage; res = getrusage(RUSAGE_CHILDREN, &usage); if (res == -1) MPROF_RTS_ERROR((VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("getrusage"), CALLFROM, errno)); child_user = (usage.ru_utime.tv_sec * (gtm_uint64_t)1000000) + usage.ru_utime.tv_usec; child_system = (usage.ru_stime.tv_sec * (gtm_uint64_t)1000000) + usage.ru_stime.tv_usec; return; } #else STATICFNDEF void get_cputime (ext_tms *curr) { int4 cpu_time_used; int status; int jpi_code = JPI$_CPUTIM; if ((status = lib$getjpi(&jpi_code, &process_id, 0, &cpu_time_used, 0, 0)) != SS$_NORMAL) MPROF_RTS_ERROR((VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("LIB$GETJPI"), CALLFROM, status)); curr->tms_utime = cpu_time_used; curr->tms_stime = 0; return; } #endif /* Enables tracing and ensures that all critical structures are initialized */ void turn_tracing_on(mval *gvn, boolean_t from_env, boolean_t save_gbl) { ext_tms curr; trace_entry tmp_trc_tbl_entry; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; mdb_ch_set = !from_env; /* if tracing is on explicitly, or if it is implicit with saving */ if (save_gbl || !from_env) { if (is_tracing_on) { gtm_putmsg(VARLSTCNT(1) ERR_TRACINGON); return; } if ((0 == gvn->str.len) || ('^' != gvn->str.addr[0])) MPROF_RTS_ERROR((VARLSTCNT(4) ERR_NOTGBL, 2, gvn->str.len, gvn->str.addr)); } /* the following should only be a one-time operation */ if (!TREF(mprof_ptr)) { TREF(mprof_ptr) = (mprof_wrapper *)malloc(SIZEOF(mprof_wrapper)); memset(TREF(mprof_ptr), 0, SIZEOF(mprof_wrapper)); } /* only need to have the gvn if we are going to save the data */ if (save_gbl && (0 < gvn->str.len)) { parse_gvn(gvn); (TREF(mprof_ptr))->gbl_to_fill = *gvn; (TREF(mprof_ptr))->gbl_to_fill.str.addr = (char *)malloc(gvn->str.len); /* len was already set up */ memcpy((TREF(mprof_ptr))->gbl_to_fill.str.addr, gvn->str.addr, gvn->str.len); } /* preallocate some space */ if (!(TREF(mprof_ptr))->pcavailbase) { (TREF(mprof_ptr))->pcavailbase = (char **)malloc(PROFCALLOC_DSBLKSIZE); *(TREF(mprof_ptr))->pcavailbase = 0; } (TREF(mprof_ptr))->pcavailptr = (TREF(mprof_ptr))->pcavailbase; (TREF(mprof_ptr))->pcavail = PROFCALLOC_DSBLKSIZE - OFFSET_LEN; memset((TREF(mprof_ptr))->pcavailptr + 1, 0, (TREF(mprof_ptr))->pcavail); curr = (TREF(mprof_ptr))->tprev; TIMES(&curr); UNIX_ONLY(child_times_usec();) mprof_stack_init(); /* if tracing is started explicitly, we are in a good frame, so we can * initialize things and start counting time */ if (!from_env) { TREF(prof_fp) = mprof_stack_push(); get_entryref_information(FALSE, NULL); tmp_trc_tbl_entry.rout_name = NULL; tmp_trc_tbl_entry.label_name = NULL; (TREF(mprof_ptr))->curr_tblnd = (TREF(mprof_ptr))->head_tblnd = NULL; (TREF(prof_fp))->start.tms_stime = curr.tms_stime; (TREF(prof_fp))->start.tms_utime = curr.tms_utime; (TREF(prof_fp))->start.tms_etime = curr.tms_etime; (TREF(prof_fp))->carryover.tms_stime = 0; (TREF(prof_fp))->carryover.tms_utime = 0; (TREF(prof_fp))->carryover.tms_etime = 0; (TREF(prof_fp))->dummy_stack_count = 0; (TREF(prof_fp))->rout_name = (TREF(prof_fp))->label_name = NULL; } /* make necessary xfer_table substitutions before we begin */ if (!is_tracing_on) { POPULATE_PROFILING_TABLE(); is_tracing_on = TRUE; } /* remember if we need to save results to a global at the end */ if (!save_gbl) save_to_gbl = FALSE; mdb_ch_set = TRUE; return; } /* Disables tracing and properly disposes of allocated resources; additionally, * saves data to the database if save_to_gbl was set to TRUE */ void turn_tracing_off(mval *gvn) { DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (FALSE == is_tracing_on) return; assert(TREF(mprof_ptr)); TIMES(&(TREF(mprof_ptr))->tcurr); UNIX_ONLY(child_times_usec();) process_system = (TREF(mprof_ptr))->tcurr.tms_stime; process_user = (TREF(mprof_ptr))->tcurr.tms_utime; /* update the time of previous M line if there was one */ if (NULL != (TREF(mprof_ptr))->curr_tblnd) { UPDATE_TIME((TREF(mprof_ptr))->curr_tblnd); } if (NULL != gvn) parse_gvn(gvn); assert(!save_to_gbl || (0 != (TREF(mprof_ptr))->gbl_to_fill.str.addr)); /* if tracing was initialized from an environment variable, and it had a proper global name, save results * to that global; otherwise, just toss the collected data */ if (save_to_gbl) { if (NULL != (TREF(mprof_ptr))->head_tblnd) { UNIX_ONLY(insert_total_times(FALSE);) insert_total_times(TRUE); mprof_tree_walk((TREF(mprof_ptr))->head_tblnd); } free((TREF(mprof_ptr))->gbl_to_fill.str.addr); (TREF(mprof_ptr))->gbl_to_fill.str.addr = NULL; } is_tracing_on = (TREF(mprof_ptr))->is_tracing_ini = FALSE; mprof_stack_free(); (TREF(mprof_ptr))->pcavailptr = (TREF(mprof_ptr))->pcavailbase; (TREF(mprof_ptr))->pcavail = PROFCALLOC_DSBLKSIZE - OFFSET_LEN; CLEAR_PROFILING_TABLE(); /* restore the original xfer_table links */ TREF(prof_fp) = NULL; return; } /* Takes care of properly embedding FOR count and nesting information into MPROF * tree nodes. Called in the beginning of any FOR loop iteration. */ void forchkhandler(char *return_address) { trace_entry tmp_trc_tbl_entry; int for_level_on_line; mprof_tree *for_link, *for_node; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; get_entryref_information(TRUE, &tmp_trc_tbl_entry); /* starting tracing now; save the info about the current FOR loop */ if (FALSE == (TREF(mprof_ptr))->is_tracing_ini) { (TREF(mprof_ptr))->is_tracing_ini = TRUE; (TREF(mprof_ptr))->curr_tblnd = (TREF(mprof_ptr))->head_tblnd = (mprof_tree *)new_node(&tmp_trc_tbl_entry); (TREF(mprof_ptr))->curr_tblnd->e.count = 1; for_node = (mprof_tree *)new_for_node(&tmp_trc_tbl_entry, return_address); for_node->e.count = 1; for_node->e.loop_level = 1; (TREF(mprof_ptr))->curr_tblnd->loop_link = (mprof_tree *)for_node; return; } (TREF(mprof_ptr))->curr_tblnd = (mprof_tree *)mprof_tree_insert(&((TREF(mprof_ptr))->head_tblnd), &tmp_trc_tbl_entry); if (NULL == (TREF(mprof_ptr))->curr_tblnd->loop_link) { /* first FOR for this node */ for_node = (mprof_tree *)new_for_node(&tmp_trc_tbl_entry, return_address); for_node->e.count = 1; for_node->e.loop_level = 1; (TREF(mprof_ptr))->curr_tblnd->loop_link = (mprof_tree *)for_node; return; } /* some FORs have been already recorded for this line, so keep checking for more */ for_link = (mprof_tree *)(TREF(mprof_ptr))->curr_tblnd->loop_link; for_level_on_line = 1; while (TRUE) { /* same FOR, so just update the count */ if (for_link->e.raddr == return_address) { for_link->e.count++; break; } /* new FOR for this line */ if (NULL == for_link->loop_link) { for_node = (mprof_tree *)new_for_node(&tmp_trc_tbl_entry, return_address); for_node->e.count = 1; for_node->e.loop_level = for_level_on_line + 1; for_link->loop_link = (mprof_tree *)for_node; break; } else { /* same FOR as last one, so increase the loop level for the line and keep searching */ for_link = (mprof_tree *)for_link->loop_link; for_level_on_line++; } } return; } /* Records the typical line-to-line deltas in CPU and absolute time. * Called on each linestart and linefetch. */ void pcurrpos(void) { trace_entry tmp_trc_tbl_entry; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (NULL == TREF(prof_fp)) { INIT_PROF_FP; } assert(TREF(mprof_ptr)); assert(TREF(prof_fp)); TIMES(&(TREF(mprof_ptr))->tcurr); /* remember the new time */ /* update the time of previous M line */ if (NULL != (TREF(mprof_ptr))->curr_tblnd) { UPDATE_TIME((TREF(mprof_ptr))->curr_tblnd); } get_entryref_information(TRUE, &tmp_trc_tbl_entry); if (FALSE == (TREF(mprof_ptr))->is_tracing_ini) { (TREF(mprof_ptr))->is_tracing_ini = TRUE; (TREF(mprof_ptr))->curr_tblnd = (TREF(mprof_ptr))->head_tblnd = (mprof_tree *)new_node(&tmp_trc_tbl_entry); (TREF(mprof_ptr))->curr_tblnd->e.count = 1; } else { (TREF(mprof_ptr))->curr_tblnd = (mprof_tree *)mprof_tree_insert(&((TREF(mprof_ptr))->head_tblnd), &tmp_trc_tbl_entry); if (0 == (TREF(mprof_ptr))->curr_tblnd->e.count) (TREF(mprof_ptr))->curr_tblnd->e.count = 1; else (TREF(mprof_ptr))->curr_tblnd->e.count++; } (TREF(mprof_ptr))->tprev = (TREF(mprof_ptr))->tcurr; return; } /* Called upon entering a new label. The time account for a label excludes the time spent within any other label * called from it. An example: * * d l1 | t0 * | * | * | d l2 | t1 * | * | * | * | q t2 * | * | * | * | * | q t3 * * Results should be: * l1 label: * (t3 - t2) + (t1 - t0) * * l2 label: * (t2 - t1) */ void new_prof_frame(int real_frame) { DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (NULL == TREF(prof_fp)) { INIT_PROF_FP; } assert(TREF(mprof_ptr)); assert(TREF(prof_fp)); /* a call to a routine, not IF or FOR with a DO */ if (real_frame) { /* update the time of the line in the parent frame */ if (NULL != (TREF(mprof_ptr))->curr_tblnd) { TIMES(&(TREF(mprof_ptr))->tcurr); UPDATE_TIME((TREF(mprof_ptr))->curr_tblnd); } (TREF(prof_fp))->curr_node = (TREF(mprof_ptr))->curr_tblnd; (TREF(mprof_ptr))->curr_tblnd = NULL; /* create a new frame on the stack */ TREF(prof_fp) = mprof_stack_push(); (TREF(prof_fp))->rout_name = NULL; (TREF(prof_fp))->label_name = NULL; (TREF(prof_fp))->start = (TREF(mprof_ptr))->tprev = (TREF(mprof_ptr))->tcurr; (TREF(prof_fp))->carryover.tms_utime = 0; (TREF(prof_fp))->carryover.tms_stime = 0; (TREF(prof_fp))->carryover.tms_etime = 0; (TREF(prof_fp))->dummy_stack_count = 0; } else (TREF(prof_fp))->dummy_stack_count++; return; } /* Records profiling information about the current line and/or label and, if necessary, unwinds * frames off the MPROF stack. Called before leaving a label. */ void unw_prof_frame(void) { trace_entry e; trace_entry tmp_trc_tbl_entry; mprof_tree *t; ext_tms carryover; stack_frame *save_fp; gtm_uint64_t frame_usr_time, frame_sys_time, frame_elp_time; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; assert(TREF(mprof_ptr)); /* do not assert on prof_fp not being NULL, as it will be NULL in * case we quit from a function without ever disabling tracing */ if (NULL == TREF(prof_fp)) return; /* we are leaving a real frame, not an IF or FOR with a DO */ if (0 >= (TREF(prof_fp))->dummy_stack_count) { TIMES(&(TREF(mprof_ptr))->tcurr); /* update the time of last line in this frame before returning */ if (NULL != (TREF(mprof_ptr))->curr_tblnd) { UPDATE_TIME((TREF(mprof_ptr))->curr_tblnd); } get_entryref_information(TRUE, &tmp_trc_tbl_entry); /* if prof_fp is NULL, it was set so in get_entryref_information, which means * that we are not in a valid frame, so there is no point in recording timing for * some unreal label; besides, the line timing has already been updated */ if (NULL == TREF(prof_fp)) return; if ((TREF(prof_fp))->rout_name == &above_routine) { /* it should have been filled in get_entryref_information */ e.label_name = tmp_trc_tbl_entry.label_name; e.rout_name = tmp_trc_tbl_entry.rout_name; } else { /* note that this memory allocated for the label and routine names might need * to be reclaimed, so set up the corresponding flag, reset the allocation count, * and the address of the current allocation bucket */ TREF(mprof_alloc_reclaim) = TRUE; TREF(mprof_reclaim_addr) = (char *)(TREF(mprof_ptr))->pcavailptr; TREF(mprof_reclaim_cnt) = 0; e.label_name = (mident *)pcalloc(SIZEOF(mident)); e.label_name->len = (TREF(prof_fp))->label_name->len; e.label_name->addr = pcalloc((unsigned int)e.label_name->len); memcpy(e.label_name->addr, (TREF(prof_fp))->label_name->addr, (TREF(prof_fp))->label_name->len); e.rout_name = (mident *)pcalloc(SIZEOF(mident)); e.rout_name->len = (TREF(prof_fp))->rout_name->len; e.rout_name->addr = pcalloc((unsigned int)e.rout_name->len); memcpy(e.rout_name->addr, (TREF(prof_fp))->rout_name->addr, (TREF(prof_fp))->rout_name->len); TREF(mprof_alloc_reclaim) = FALSE; /* memory should not have to be reclaimed after this point, * so stop updating the count */ } e.line_num = -1; /* insert/find a frame entry into/in the MPROF tree, -1 indicating that it is not a * real line number, but rather an aggregation of several lines (comprising a label) */ t = mprof_tree_insert(&((TREF(mprof_ptr))->head_tblnd), &e); TREF(mprof_reclaim_cnt) = 0; /* reset the memory allocation count */ /* update count and timing (from prof_fp) of frame I'm leaving */ t->e.count++; frame_usr_time = ((TREF(mprof_ptr))->tcurr.tms_utime - (TREF(prof_fp))->start.tms_utime - (TREF(prof_fp))->carryover.tms_utime); frame_sys_time = ((TREF(mprof_ptr))->tcurr.tms_stime - (TREF(prof_fp))->start.tms_stime - (TREF(prof_fp))->carryover.tms_stime); frame_elp_time = ((TREF(mprof_ptr))->tcurr.tms_etime - (TREF(prof_fp))->start.tms_etime - (TREF(prof_fp))->carryover.tms_etime); t->e.usr_time += frame_usr_time; t->e.sys_time += frame_sys_time; t->e.elp_time += frame_elp_time; /* not the first frame on the stack */ if ((TREF(prof_fp))->prev) { carryover = (TREF(prof_fp))->carryover; /* move back up to parent frame */ TREF(prof_fp) = mprof_stack_pop(); (TREF(prof_fp))->carryover.tms_utime += (frame_usr_time + carryover.tms_utime); (TREF(prof_fp))->carryover.tms_stime += (frame_sys_time + carryover.tms_stime); (TREF(prof_fp))->carryover.tms_etime += (frame_elp_time + carryover.tms_etime); /* restore the context of the parent frame */ (TREF(mprof_ptr))->tprev = (TREF(mprof_ptr))->tcurr; (TREF(mprof_ptr))->curr_tblnd = (TREF(prof_fp))->curr_node; (TREF(prof_fp))->curr_node = NULL; } else { /* This should only be true only if the View command is not at the top-most stack level, in which case * add profiling information for the current frame. To prevent stack underflow, add a new frame before * unwinding from this frame. */ mprof_stack_pop(); (TREF(mprof_ptr))->tprev = (TREF(mprof_ptr))->tcurr; (TREF(mprof_ptr))->curr_tblnd = NULL; TREF(prof_fp) = mprof_stack_push(); save_fp = frame_pointer; # ifdef GTM_TRIGGER if (frame_pointer->type & SFT_TRIGR) { /* in a trigger base frame, old_frame_pointer is NULL */ assert(NULL == frame_pointer->old_frame_pointer); /* have a trigger baseframe, pick up stack continuation frame_pointer stored by base_frame() */ frame_pointer = *(stack_frame **)(frame_pointer + 1); } else # endif frame_pointer = frame_pointer->old_frame_pointer; /* if frame_pointer is NULL, we are likely dealing with call-ins, so there is no point trying to unwind * any further; just restore the frame_pointer and return */ if (!frame_pointer) { frame_pointer = save_fp; return; } get_entryref_information(FALSE, NULL); frame_pointer = save_fp; /* if for some reason we made it all the way here, but prof_fp was still set to NULL by * get_entryref_information(), then just silently quit out of this routine to prevent unwinding into some * nowhere land */ if (NULL == TREF(prof_fp)) return; (TREF(prof_fp))->start = (TREF(mprof_ptr))->tcurr; (TREF(prof_fp))->carryover.tms_utime = 0; (TREF(prof_fp))->carryover.tms_stime = 0; (TREF(prof_fp))->carryover.tms_etime = 0; /* tag it, so that next time it picks up label/routine info from current loc */ (TREF(prof_fp))->rout_name = (mident *)&above_routine; (TREF(prof_fp))->label_name = NULL; (TREF(prof_fp))->dummy_stack_count = 0; } } else (TREF(prof_fp))->dummy_stack_count--; return; } /* Allocate storage for profiling information */ char *pcalloc(unsigned int n) { char **x; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; # if defined (GTM64) || defined(__osf__) || defined(VMS) n = ((n + 7) & ~7); /* same logic applied for alignment */ # else n = ((n + 3) & ~3); /* make sure that it is quad-word aligned */ # endif if (n > (TREF(mprof_ptr))->pcavail) { if (*(TREF(mprof_ptr))->pcavailptr) (TREF(mprof_ptr))->pcavailptr = (char ** )*(TREF(mprof_ptr))->pcavailptr; else { x = (char **)malloc(PROFCALLOC_DSBLKSIZE); *(TREF(mprof_ptr))->pcavailptr = (char *)x; (TREF(mprof_ptr))->pcavailptr = x; *(TREF(mprof_ptr))->pcavailptr = NULL; } (TREF(mprof_ptr))->pcavail = PROFCALLOC_DSBLKSIZE - OFFSET_LEN; memset((TREF(mprof_ptr))->pcavailptr + 1, 0, (TREF(mprof_ptr))->pcavail); } (TREF(mprof_ptr))->pcavail -= n; if (TREF(mprof_alloc_reclaim)) (TREF(mprof_reclaim_cnt)) += n; /* update the memory allocation count if needed */ assert((TREF(mprof_ptr))->pcavail >= 0); return (char *)(TREF(mprof_ptr))->pcavailptr + (TREF(mprof_ptr))->pcavail + OFFSET_LEN; } /* Reclaim storage previously allocated by pcalloc(). */ void mprof_reclaim_slots(void) { int alloc_diff; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (0 < TREF(mprof_reclaim_cnt)) /* in case some memory needs to be reclaimed, do so */ { alloc_diff = (PROFCALLOC_DSBLKSIZE - (TREF(mprof_ptr))->pcavail - OFFSET_LEN); if (alloc_diff >= TREF(mprof_reclaim_cnt)) /* allocation did not need a new bucket, we are good */ (TREF(mprof_ptr))->pcavail += TREF(mprof_reclaim_cnt); else { /* go back to the previous allocation bucket and set the pcavail accordingly for the older bucket */ (TREF(mprof_ptr))->pcavailptr = (char **)TREF(mprof_reclaim_addr); (TREF(mprof_ptr))->pcavail = (TREF(mprof_reclaim_cnt) - alloc_diff); } } } /* Writes the data into the global. */ void crt_gbl(mprof_tree *p, boolean_t is_for) { char *c_top, *c_ref, ch; int count, arg_index, subsc_len, tmp_str_len; INTPTR_T start_point; mval data; char dataval[96]; /* big enough for data value */ unsigned char subsval[12]; /* see i2asc + 1 for null char */ unsigned char asc_line_num[12]; /* to hold the ascii equivalent of the line_num */ unsigned char *tmpnum, *end; mval *spt; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (0 == p->e.count) return; count = (int)(TREF(mprof_ptr))->gvargs.count; spt = &(TREF(mprof_ptr))->subsc[count]; /* global name --> ^PREFIX(, "rout-name", "label-name", "line-num", "forloop") */ spt->mvtype = MV_STR; spt->str.len = p->e.rout_name->len; spt->str.addr = (char *)pcalloc((unsigned int)spt->str.len); memcpy(spt->str.addr, p->e.rout_name->addr, spt->str.len); (TREF(mprof_ptr))->gvargs.args[count++] = spt++; spt->mvtype = MV_STR; if (0 != p->e.label_name->len) { spt->str.len = p->e.label_name->len; spt->str.addr = (char *)pcalloc((unsigned int)spt->str.len); memcpy(spt->str.addr, p->e.label_name->addr, spt->str.len); } else { /* place holder before first label */ spt->str.len = SIZEOF(MPROF_NULL_LABEL) - 1; spt->str.addr = (char *)pcalloc((unsigned int)spt->str.len); memcpy(spt->str.addr, MPROF_NULL_LABEL, spt->str.len); } (TREF(mprof_ptr))->gvargs.args[count++] = spt++; spt->mvtype = MV_STR; if (0 <= p->e.line_num) { tmpnum = i2asc(asc_line_num, (unsigned int) p->e.line_num); spt->str.len = INTCAST(tmpnum - asc_line_num); spt->str.addr = (char *)pcalloc((unsigned int)spt->str.len); memcpy(spt->str.addr, asc_line_num, spt->str.len); (TREF(mprof_ptr))->gvargs.args[count] = spt; count++; spt++; } /* for FOR loops */ if (is_for) { spt->mvtype = MV_STR; spt->str.len = SIZEOF(MPROF_FOR_LOOP) - 1; spt->str.addr = (char *)pcalloc(SIZEOF(MPROF_FOR_LOOP) - 1); memcpy(spt->str.addr, MPROF_FOR_LOOP, spt->str.len); (TREF(mprof_ptr))->gvargs.args[count++] = spt++; /* write for level into the subscript as well */ spt->mvtype = MV_STR; tmpnum = i2asc(subsval, p->e.loop_level); spt->str.len = INTCAST(tmpnum - subsval); spt->str.addr = (char *)pcalloc((unsigned int)spt->str.len); memcpy(spt->str.addr, subsval, spt->str.len); (TREF(mprof_ptr))->gvargs.args[count++] = spt++; } (TREF(mprof_ptr))->gvargs.count = count; callg((INTPTR_T(*)(intszofptr_t count_arg, ...))op_gvname, (gparam_list *)&(TREF(mprof_ptr))->gvargs); (TREF(mprof_ptr))->gvargs.count = (TREF(mprof_ptr))->curr_num_subscripts; /* data --> "count:cpu-time in user mode:cpu-time in sys mode:cpu-time total" */ start_point = (INTPTR_T)&dataval[0]; /* get count */ tmpnum = (unsigned char *)&dataval[0]; end = i2asc(tmpnum, p->e.count); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); /* for non-FOR stuff get CPU time as well */ if (!is_for) { *tmpnum = ':'; tmpnum++; end = i2ascl(tmpnum, p->e.usr_time); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); # ifndef VMS *tmpnum = ':'; tmpnum++; end = i2ascl(tmpnum, p->e.sys_time); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); *tmpnum = ':'; tmpnum++; end = i2ascl(tmpnum, p->e.sys_time + p->e.usr_time); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); *tmpnum = ':'; tmpnum++; end = i2ascl(tmpnum, p->e.elp_time); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); # endif } data.mvtype = MV_STR; data.str.len = (((INTPTR_T)tmpnum - start_point) > 0) ? ((int)((INTPTR_T)tmpnum - start_point)) : ((int)(start_point - (INTPTR_T)tmpnum)); data.str.addr = (char *)pcalloc((unsigned int)data.str.len); memcpy(data.str.addr, dataval, data.str.len); op_gvput(&data); return; } /* Save total CPU times for the current and all child processes */ STATICFNDEF void insert_total_times(boolean_t for_process) { int count; INTPTR_T start_point; mval data; char dataval[96]; /* big enough for data value */ unsigned char *tmpnum, *end; mval *spt; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; count = (int)(TREF(mprof_ptr))->gvargs.count; spt = &(TREF(mprof_ptr))->subsc[count]; spt->mvtype = MV_STR; if (for_process) { spt->str.len = SIZEOF(PROCESS_TIME) - 1; spt->str.addr = (char *)pcalloc(SIZEOF(PROCESS_TIME) - 1); memcpy(spt->str.addr, PROCESS_TIME, spt->str.len); } else { spt->str.len = SIZEOF(CHILDREN_TIME) - 1; spt->str.addr = (char *)pcalloc(SIZEOF(CHILDREN_TIME) - 1); memcpy(spt->str.addr, CHILDREN_TIME, spt->str.len); } (TREF(mprof_ptr))->gvargs.args[count++] = spt++; (TREF(mprof_ptr))->gvargs.count = count; callg((INTPTR_T(*)(intszofptr_t count_arg, ...))op_gvname, (gparam_list *)&(TREF(mprof_ptr))->gvargs); (TREF(mprof_ptr))->gvargs.count = (TREF(mprof_ptr))->curr_num_subscripts; start_point = (INTPTR_T)&dataval[0]; tmpnum = (unsigned char *)&dataval[0]; if (for_process) end = i2ascl(tmpnum, process_user); else end = i2ascl(tmpnum, child_user); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); *tmpnum = ':'; tmpnum++; if (for_process) end = i2ascl(tmpnum, process_system); else end = i2ascl(tmpnum, child_system); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); *tmpnum = ':'; tmpnum++; if (for_process) end = i2ascl(tmpnum, process_user + process_system); else end = i2ascl(tmpnum, child_user + child_system); tmpnum += ((end - tmpnum) > 0) ? (end - tmpnum) : (tmpnum - end); data.mvtype = MV_STR; data.str.len = (((INTPTR_T)tmpnum - start_point) > 0) ? ((int)((INTPTR_T)tmpnum - start_point)) : ((int)(start_point - (INTPTR_T)tmpnum)); data.str.addr = (char *)pcalloc((unsigned int)data.str.len); memcpy(data.str.addr, dataval, data.str.len); op_gvput(&data); return; } /* Fills the information about the current location in the code into the passed variable. */ STATICFNDEF void get_entryref_information(boolean_t line, trace_entry *tmp_trc_tbl_entry) { boolean_t line_reset; lab_tabent *max_label, *label_table, *last_label; rhdtyp *routine; stack_frame *fp; int status; unsigned char *addr, *out_addr; int4 *line_table, *last_line, len, ct; int4 offset, in_addr_offset; unsigned long user_time, system_time; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; line_reset = FALSE; for (fp = frame_pointer; fp; fp = fp->old_frame_pointer) { # ifdef GTM_TRIGGER if (fp->type & SFT_TRIGR) { assert(NULL == fp->old_frame_pointer); /* have a trigger baseframe, pick up stack continuation frame_pointer stored by base_frame() */ fp = *(stack_frame **)(fp + 1); } # endif assert(fp); if (ADDR_IN_CODE(fp->mpc, fp->rvector)) { if (line_reset) addr = fp->mpc + 1; else addr = fp->mpc; break; } else { if (fp->type & SFT_ZTRAP || fp->type & SFT_DEV_ACT) line_reset = TRUE; } } if (NULL == fp) { TREF(prof_fp) = NULL; return; } routine = fp->rvector; routine = CURRENT_RHEAD_ADR(routine); label_table = LABTAB_ADR(routine); last_label = label_table + routine->labtab_len; max_label = label_table++; while (label_table < last_label) { if (addr > LABEL_ADDR(routine, label_table)) { if (max_label->LABENT_LNR_OFFSET <= label_table->LABENT_LNR_OFFSET) max_label = label_table; } label_table++; } if (line) { tmp_trc_tbl_entry->rout_name = &routine->routine_name; tmp_trc_tbl_entry->label_name = &max_label->lab_name; } if (NULL == (TREF(prof_fp))->rout_name) (TREF(prof_fp))->rout_name = &routine->routine_name; if (NULL == (TREF(prof_fp))->label_name) (TREF(prof_fp))->label_name = &max_label->lab_name; if (!line) return; line_table = LABENT_LNR_ENTRY(routine, max_label); offset = 0; in_addr_offset = (int4)CODE_OFFSET(routine, addr); last_line = LNRTAB_ADR(routine); last_line += routine->lnrtab_len; for( ; ++line_table < last_line ; offset++) { if (in_addr_offset <= *line_table) break; } tmp_trc_tbl_entry->line_num = offset; return; } /* Parses the global variable name that the information will be dumped into, to make sure it is a valid gvn */ STATICFNDEF void parse_gvn(mval *gvn) { boolean_t dot_seen; mval *spt; char *c_top, *c_ref, ch; unsigned int count = 0; char *mpsp; /* pointer into mprof_mstr area */ DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; c_ref = gvn->str.addr; c_top = c_ref + gvn->str.len; if (!gvn->str.len || ('^' != *c_ref++)) MPROF_RTS_ERROR((VARLSTCNT(4) ERR_NOTGBL, 2, gvn->str.len, gvn->str.addr)); if (mprof_mstr.len < 4 * gvn->str.len) { /* We are going to return an array of mvals pointing to global-name and subscript. We should * never be needing more than 4 * gvn->str.len since the only expandable entity that can be * passed is $j which uses up at least 3 characters (including '_' or ',') and expands to a * maximum of 10 characters (see djbuff in getjobname.c). */ if (mprof_mstr.len) { assert(mprof_mstr.addr); free(mprof_mstr.addr); } mprof_mstr.len = 4 * gvn->str.len; mprof_mstr.addr = (char *)malloc(mprof_mstr.len); } mpsp = mprof_mstr.addr; /* parse the global variable passed to insert the information */ spt = &(TREF(mprof_ptr))->subsc[0]; spt->mvtype = MV_STR; spt->str.addr = mpsp; ch = *mpsp++ = *c_ref++; if (!ISALPHA_ASCII(ch) && ('%' != ch)) RTS_ERROR_VIEWNOTFOUND("Invalid global name"); for ( ; (c_ref < c_top) && ('(' != *c_ref); ) { ch = *mpsp++ = *c_ref++; if (!ISALNUM_ASCII(ch)) RTS_ERROR_VIEWNOTFOUND("Invalid global name"); } spt->str.len = INTCAST(mpsp - spt->str.addr); (TREF(mprof_ptr))->gvargs.args[count++] = spt++; spt->str.addr = (char *)mpsp; /* process subscripts, if any */ if (c_ref++ < c_top) { for ( ; c_ref < c_top; ) { spt->mvtype = MV_STR; ch = *c_ref; if ('\"' == ch) { c_ref++; for ( ; ; ) { if (c_ref == c_top) RTS_ERROR_VIEWNOTFOUND("Right parenthesis expected"); if ('\"' == *c_ref) { if (++c_ref == c_top) RTS_ERROR_VIEWNOTFOUND("Right parenthesis expected"); if (*c_ref != '\"') break; } *mpsp++ = *c_ref++; } } else if ('$' == ch) { if (++c_ref == c_top) RTS_ERROR_VIEWNOTFOUND("Intrinsic value is incomplete"); if (*c_ref != 'J' && *c_ref != 'j') RTS_ERROR_VIEWNOTFOUND("Intrinsic value passed is not $j"); c_ref++; /* past 'J' */ if ((c_ref < c_top) && (ISALPHA_ASCII(*c_ref))) { ch = *c_ref; if (c_top >= c_ref + 2) { if (((('O' == ch) || ('o' == ch)) && (('B' == *(c_ref + 1)) || ('b' == *(c_ref + 1))))) c_ref += 2; else RTS_ERROR_VIEWNOTFOUND("Intrinsic value passed is not $j"); } else RTS_ERROR_VIEWNOTFOUND("Intrinsic value is incomplete"); } assert(10 > dollar_job.str.len); /* to take care of 4 * gvn->str.len allocation above */ memcpy(mpsp, dollar_job.str.addr, dollar_job.str.len); mpsp += dollar_job.str.len; } else { dot_seen = FALSE; if (!ISDIGIT_ASCII(ch) && ('.' != ch) && ('-' != ch) && ('+' != ch)) RTS_ERROR_VIEWNOTFOUND("Improperly formatted numeric subscript"); if ('.' == ch) dot_seen = TRUE; *mpsp++ = *c_ref++; for ( ; ; ) { if (c_ref == c_top) RTS_ERROR_VIEWNOTFOUND("Right parenthesis expected"); if (!ISDIGIT_ASCII(*c_ref)) { if ('.' != *c_ref) break; else if (!dot_seen) dot_seen = TRUE; else RTS_ERROR_VIEWNOTFOUND("Improperly formatted numeric subscript"); } *mpsp++ = *c_ref++; } } if (c_ref == c_top) RTS_ERROR_VIEWNOTFOUND("Right parenthesis expected"); if ('_' == *c_ref) { c_ref++; continue; } spt->str.len = INTCAST(mpsp - spt->str.addr); if (MAX_GVSUBSCRIPTS <= count) MPROF_RTS_ERROR((VARLSTCNT(1) ERR_MAXNRSUBSCRIPTS)); (TREF(mprof_ptr))->gvargs.args[count++] = spt++; if (',' != *c_ref) break; spt->str.addr = mpsp; c_ref++; } assert(c_ref <= c_top); if (c_ref >= c_top) RTS_ERROR_VIEWNOTFOUND("Right parenthesis expected"); if (')' != *c_ref) RTS_ERROR_VIEWNOTFOUND("Right parenthesis expected"); if (++c_ref < c_top) RTS_ERROR_VIEWNOTFOUND("There are trailing characters after the global name"); } assert((char *)mpsp <= mprof_mstr.addr + mprof_mstr.len); /* ensure we haven't overrun the malloced buffer */ (TREF(mprof_ptr))->gvargs.count = count; (TREF(mprof_ptr))->curr_num_subscripts = (int)(TREF(mprof_ptr))->gvargs.count; return; } void stack_leak_check(void) { int var_on_cstack; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; # ifdef UNIX if (GTMTRIG_ONLY((0 < gtm_trigger_depth) ||) (0 < TREF(gtmci_nested_level))) return; # endif if (NULL == var_on_cstack_ptr) var_on_cstack_ptr = &var_on_cstack; if ((&var_on_cstack != var_on_cstack_ptr) # ifdef __i386 /* for 32-bit Linux allow a two pointer variation to accommodate ZHELP */ && ((SIZEOF(var_on_cstack) * 2) < ABS(&var_on_cstack - var_on_cstack_ptr)) # endif ) GTMASSERT; return; }