La instrumentación se refiere a la capacidad de supervisar y medir el rendimiento de una aplicación. Si nuestra aplicaciones están instrumentadas, en cualquier momento podremos saber qué rutina es la que consume más recursos, en qué porcentaje de avance está aquel proceso batch que parece no terminar, podríamos hacer trace a un módulo, independientemente del usuario que lo esté ejecutando, etc.
Luego de varios años de venir realizando consultorías, he encontrado que muy pocos implementan una instrumentación de sus aplicaciones, quizás sea porque no saben que esto es posible, por ello a continuación les mostraré cómo hacerlo y los beneficios inmediatos que pueden lograrse con muy poco esfuerzo.
La pieza central para lograr la instrumentación de las aplicaciones viene a ser el paquete DBMS_APPLICATION_INFO. Empezaremos por conocer los valores que podemos establecer y los métodos principales que usaremos en nuestro proceso de instrumentación.
module_name
Es el nombre del módulo que está actualmente en ejecución. Se trata de una cadena de hasta 48 bytes, si es más larga se trunca silenciosamente.
action_name
Es el nombre de la acción vigente para el módulo vigente. Se trata de unan cadena de hasta 32 bytes, si es más larga se trunca silenciosamente.
set_module
dbms_application_info.set_module(
module_name=> 'Facturacion',
action_name=> 'Obtencion.Numero.Factura' );
set_action
dbms_application_info.set_action(
action_name=> 'Calculo.Impuestos' );
set_session_longops
dbms_application_info.set_session_longops(
rindex => v_rindex, --identificador de la fila en v$session_longops
slno => v_slno, --para uso interno, no modificar
op_name => 'Recalculo', --nombre que le asignamos a la tarea
target_desc => 'Cheque', --descripción del objeto manipulado
sofar => v_sofar, --indicador de cuanto trabajo ya se ha hecho
totalwork => v_totalwork, --indicador del total de trabajo por hacer
context => v_nro_cheque, --cualquier numero que se desee almacenar
units => 'Cheques'); --unidad en que se representa sofar y totalwork
DECLARE
v_rindex PLS_INTEGER;
v_slno PLS_INTEGER;
v_totalwork NUMBER;
v_sofar NUMBER;
v_obj PLS_INTEGER;
v_dname dept.dname%TYPE;
BEGIN
dbms_application_info.set_module (
module_name => 'Listado.Empleados',
action_name=> 'Total.Empleados' );
v_rindex := dbms_application_info.set_session_longops_nohint;
v_sofar := 0;
SELECT count(*)
INTO v_totalwork
FROM emp;
dbms_application_info.set_action (
action_name=> 'Bucle.Empleados' );
FOR c_emp IN (
SELECT empno, ename, deptno
FROM emp )
LOOP
SELECT dname INTO v_dname
FROM dept
WHERE deptno = c_emp.deptno;
dbms_output.put_line(
c_emp.empno||':'||c_emp.ename||':'||v_dname);
v_sofar := v_sofar + 1;
dbms_application_info.set_session_longops(
rindex => v_rindex,
slno => v_slno,
op_name => 'Procesando Empleados',
target_desc => 'Empleado',
sofar => v_sofar,
totalwork => v_totalwork,
context => c_emp.empno,
units => 'Empleados');
dbms_lock.sleep( 2 ); --artificio para hacer lento el bucle
END LOOP;
dbms_application_info.set_module (
module_name => '',
action_name=> '' );
END;
SYS@orcl> SELECT message, ROUND(sofar/totalwork,2)*100 "%Avance",
time_remaining falta, context empno
from v$session_longops
where time_remaining > 0;
MESSAGE
-----------------------------------------------------------
%Avance FALTA CONTEXT
---------- ---------- ----------
Procesando Empleados: Empleado : 1 out of 14 Empleados done
7 30 7369
SYS@orcl > /
MESSAGE
-----------------------------------------------------------
%Avance FALTA CONTEXT
---------- ---------- ----------
Procesando Empleados: Empleado : 3 out of 14 Empleados done
21 11 7521
SYS@orcl> /
MESSAGE
-----------------------------------------------------------
%Avance FALTA CONTEXT
---------- ---------- ----------
Procesando Empleados: Empleado : 13 out of 14 Empleados done
93 2 7902
SYS@orcl> /
no rows selected
También hemos identificando la rutina en sí (set_module) y las diferentes secciones al interior del mismo (set_action), esta información es visible desde algunas vistas del catálogo, como es el caso de v$sql.
SYS@orcl> SELECT action, executions, sql_text
FROM v$sql
WHERE module = 'Listado.Empleados';
ACTION EXECUTIONS SQL_TEXT
-------------------- ---------- -----------------------------------------
Bucle.Empleados 1 SELECT EMPNO, ENAME, DEPTNO FROM EMP
Total.Empleados 1 SELECT COUNT(*) FROM EMP
Nombre.Departamento 14 SELECT DNAME FROM DEPT WHERE DEPTNO = :B1
Ahora la labor de hallar el origen de una sentencia SQL se simplifica pues podemos identificar rápidamente el módulo y la acción de la cual proviene, de otra forma tendríamos que enviar la sentencia SQL al equipo de desarrollo para que ellos a su vez identifiquen la aplicación de la cual proviene, labor cuyo grado de dificultad depende del nivel de control que se tenga sobre el código fuente.
Si todas nuestras aplicaciones están debidamente instrumentadas, en todo momento podemos saber cómo los diversos módulos y acciones consumen los recursos, para ellos nos valemos adicionalmente de v$active_session_history.
SYS@orcl> SELECT b.name service, NVL(module,'Sin Nombre') module,
ROUND(100*(COUNT(*)/sum(COUNT(*)) OVER()), 1) pct
FROM v$active_session_history a, v$services b
WHERE a.service_hash = b.name_hash
AND session_type <> 'BACKGROUND'
AND sample_time > systimestamp - 1/288 --ultimos 5 minutos
GROUP BY b.name, module
ORDER BY pct desc;
SERVICE MODULE PCT
---------------- ------------------ ------
SYS$USERS Facturacion 53
SYS$USERS Almacenes 44.1
orcl.oracle.com OEM.DefaultPool 1.9
orcl.oracle.com OEM.Loader .6
orcl.oracle.com Sin Nombre .4