While most of the preceding chapters have focused on adding more statements to your procedures to support instrumentation, we haven't discussed the possible performance implications of doing this.
It is certainly true that adding extra instructions to your code will lead to an increase in runtime, but there are things we can do to in both coding and configuration to ensure that the impact of instrumentation is as small as possible.
We've already seen that the behaviour of BMC_DEBUG can be dynamically controlled via the parameter table, and some of its features can be disabled using global parameters if they are not required. It is also possible to change the way in which instrumentation is implemented in your code to make it more efficient.
You can also refer to Recommended Parameter Values for more information on which parameter values to use on your live system.
Deciding which procedures to instrument
It is possible that some of your procedures and functions may contain very small numbers of instructions, and might be invoked very frequently, so you might decide not to instrument them for reasons of efficiency.
For example, if you happened to have a function like this :-
FUNCTION my_divide (in_num1 IN NUMBER, in_num2 IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN in_num1 / in_num2;
END;
The overhead of calling BEGINCALL and ENDCALL in such a function would be far greater than the code in the function body, so it would be reasonable to decide not to call them.
However, it is still possible to add some instrumentation features to such a function, even if BEGINCALL and ENDCALL are not invoked. We could log the parameters with were passed in via a MSG call such as :-
FUNCTION my_divide (in_num1 IN NUMBER, in_num2 IN NUMBER) RETURN NUMBER IS
BEGIN
bmc_debug.msg(msglevel => 7,
msg => 'Passed parameters are '||in_num1||' and '||in_num2,
force_package => $$PLSQL_UNIT,
force_procedure => 'MY_DIVIDE');
RETURN in_num1 / in_num2;
END;
The call to MSG sets the package and procedure names for this message only by using the force_package and force_procedure parameters, to ensure that when the message is logged it names the correct program unit as its source. It is important to remember that the DEBUG LEVEL which MSG considers when deciding whether the message should be logged will be the one assigned by the calling procedure, as its entry will be on the top of the call stack.
It is also possible to use the generic exception handler with such a function to ensure that unhandled exceptions are logged :-
FUNCTION my_divide (in_num1 IN NUMBER, in_num2 IN NUMBER) RETURN NUMBER IS
BEGIN
bmc_debug.msg(msglevel => 7,
msg => 'Passed parameters were '||in_num1||' and '||in_num2,
force_package => $$PLSQL_UNIT,
force_procedure => 'MY_DIVIDE');
RETURN in_num1 / in_num2;
EXCEPTION
WHEN OTHERS THEN
bmc_error.error_handle(force_package => $$PLSQL_UNIT,
force_procedure => 'MY_DIVIDE');
RAISE;
END;
Again, as we haven't called BEGINCALL to place the function details onto the call stack, we use the force_package and force_procedure parameters to ERROR_HANDLE to ensure the exception details are logged correctly. When these values are passed, it also tells ERROR_HANDLE not to call ENDCALL to remove the top entry on the call stack.
Avoid forming complex messages which won't be logged
We've already seen in Interrogating the Call Stack that we can check the DEBUG LEVEL that an instrumented procedure is running at, and make decisions based on it. For example, we could decide not to run a SELECT which retrieves information for a message, if the message is not doing to be logged :-
IF bmc_debug.get_debug_level >= 7 THEN SELECT count(*) INTO ct FROM orders where cust_id = in_cust_id; bmc_debug.msg(7,'Total orders for this customer : '||ct); END IF; ct := 0; -- Prevent any side-effects
You might also want to consider doing this if you message contains a complex concatenation e.g.
IF bmc_debug.get_debug_level >= 7 THEN bmc_debug.msg(7,'id1='||id1||',id2='||id2||',id3='||id3); END IF;
However, you need to remember that by doing this you are restricting use of some of the inbuilt BMC_DEBUG functionality, as the message will not be written to the message store or to the CLIENT_INFO column via DBMS_APPLICATION_INFO. If you just want to restrict the use of those features to certain messages, this can be done via global parameters.
Restricting use of the message store and DBMS_APPLICATION_INFO
Something to bear in mind when adding messages to your code is that the MSG procedure does more than just checking the DEBUG LEVEL to decide whether to write the message to the log table.
Consider the following example :-
BEGIN bmc_debug.begincall('TEST','LOOPTEST',3); -- Force DEBUG LEVEL 3 FOR i IN 1..5000 LOOP bmc_debug.msg(99,'This is a message'); END LOOP; bmc_debug.endcall('TEST','LOOPTEST'); END;
This code sets the DEBUG LEVEL to 3 via a parameter, then calls the BMC_DEBUG.MSG procedure 5000 times, but with a msglevel which is above the current DEBUG LEVEL. You might think that this block does almost nothing outside the BEGINCALL and ENDCALL calls, but it will actually
Manipulate the message store 5000 times, extending the store up to its maximum size then overwriting the oldest entries as necessary
Call DBMS_APPLICATION_INFO 5000 times, to write the message text to the CLIENT_INFO column visible in V$SESSION
To avoid such work being performed for very detailed messages, it is possible to place an upper limit on the message levels for which these actions are performed.
This is done via the following global parameters :-
SQL> SELECT param_name,param_value FROM bmc_debug_global_parameter WHERE param_name like '%MAX MSG LEVEL'; PARAM_NAME PARAM_VALUE ------------------------------ ------------------------------ MSG STORE MAX MSG LEVEL 9999 CLIENT INFO MAX MSG LEVEL 9999
The default values for both of these parameters is 9999, meaning every message level will be written to the message store and to CLIENT INFO. If we were to modify both of these parameters to a value of 50, when the example block was rerun the calls to MSG would do nothing, as the message still would not be written to the log table due to the DEBUG LEVEL, but its msglevel would also be above the thresholds defined by the global parameters, so the message would not be written to the message store or to CLIENT_INFO.
It would still be possible to have the messages written to the log table by running the code with a DEBUG LEVEL of 99 or above.
Disabling Instrumentation
One of the biggest concerns about running instrumented code in a live environment is the performance impact it will have, and while its behaviour can be controlled by use of the parameter tables, there is a global parameter which will completely disabled all BMC_DEBUG functionality, leaving instrumented code to run as if the instrumentation was not present.
The global parameter which does this is called KILLSWITCH ENGAGE, and if this is set to TRUE then all BMC_DEBUG procedures and functions will exit as soon as they are called and will do nothing. This parameter is provided as a safeguard against instrumentation causing unexpected side-effects or performance issues in a live environment.