El programa ZINI_JOB_WATCHERS es una herramienta de monitorización de jobs de fondo (BTC) desarrollada en ABAP OOP. A través de su pantalla de selección recibe el nombre del job a vigilar (que admite comodines como * y +), opcionalmente su número, un rango de fechas, los minutos de espera entre reintentos (por defecto 5) y la dirección de correo de destino. El programa funciona en dos modos según cómo se ejecute: en modo online (ejecución interactiva por un usuario) consulta los jobs que están actualmente activos y que coinciden con el nombre y el rango de fechas, los muestra en un listado ALV emergente para que el usuario seleccione uno, y a partir de esa elección programa automáticamente un nuevo job que relanza este mismo programa pasándole los parámetros y el número del job seleccionado. De este modo, la vigilancia continúa de forma desatendida en segundo plano.
En modo fondo (cuando el programa se ejecuta como job) comprueba si el job monitorizado sigue en estado activo. Si todavía se está ejecutando, el programa se reprograma a sí mismo para volver a comprobarlo pasados los minutos indicados (controlando correctamente el cambio de día mediante timestamps) y finaliza, repitiendo ese ciclo hasta que el job termine. Cuando detecta que el job ya ha finalizado, envía un correo electrónico informativo mediante la clase zini_cl_email, cuyo asunto es el nombre del job y cuyo cuerpo incluye el mensaje "El job ha finalizado" junto con el estado final traducido a texto legible, la máquina (servidor) donde se ejecutó, el mandante y las horas de inicio y fin. Para garantizar que este aviso llegue de inmediato y no quede retenido en la cola de la transacción SOST, el envío se marca como inmediato afectando únicamente a ese correo concreto.
Requiere la clase de envio de correos
- *&---------------------------------------------------------------------*
- *& Report ZINI_JOB_WATCHERS
- *&---------------------------------------------------------------------*
- *& Monitor de JOBs:
- *& - Online: lista los jobs activos que coinciden con el nombre y el
- *& rango de fechas. El usuario selecciona uno y se programa
- *& un job que vuelve a ejecutar este mismo programa con el
- *& numero de job elegido.
- *& - Fondo : comprueba si el job indicado sigue activo. Si lo esta, se
- *& reprograma a si mismo 5 minutos despues. Si no, envia un
- *& correo informando del estado final del job.
- *&---------------------------------------------------------------------*
- REPORT ZINI_JOB_WATCHERS.
- *--[ Include con la clase de envio de correo (zini_cl_email)
- INCLUDE zini_i_email. "https://wiki.bergonzini.com/pmwiki.php?n=ABAP.SSENDMAIL
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- *--[ Pantalla de seleccion
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-t01.
- PARAMETERS p_job TYPE btcjob OBLIGATORY. " Nombre del job
- PARAMETERS p_jobn TYPE btcjobcnt. " Numero del job
- SELECT-OPTIONS s_fecha FOR sy-datum. " Rango de fechas
- "Minutos de espera para reprogramar el job en fondo (por defecto 5)
- PARAMETERS p_delay TYPE i DEFAULT 5 OBLIGATORY. " Retardo (minutos)
- "Nota: los PARAMETERS no admiten el tipo STRING; se usa AD_SMTPADR,
- " el tipo estandar para direcciones de correo, y se convierte a
- " string al utilizar la clase de email.
- PARAMETERS p_email TYPE ad_smtpadr OBLIGATORY LOWER CASE. " Destino
- SELECTION-SCREEN END OF BLOCK b1.
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- *--[ Ajuste dinamico de la pantalla de seleccion
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- AT SELECTION-SCREEN OUTPUT.
- "El numero de job (p_jobn) solo tiene sentido en ejecucion de fondo,
- "donde se monitoriza un job concreto. En online el usuario elige el
- "job del listado, por lo que ocultamos el campo.
- LOOP AT SCREEN INTO DATA(ls_screen).
- IF ls_screen-name cp '*P_JOBN*' AND sy-batch <> abap_true.
- ls_screen-active = 0. " Ocultar el campo
- MODIFY SCREEN FROM ls_screen.
- ENDIF.
- ENDLOOP.
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- *--[ Definicion de la clase monitor
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- CLASS lcl_monitor DEFINITION FINAL.
- PUBLIC SECTION.
- TYPES tt_jobs TYPE STANDARD TABLE OF tbtco WITH DEFAULT KEY.
- "! Constructor: recibe los parametros de entrada del programa
- METHODS constructor
- IMPORTING i_job TYPE btcjob
- i_jobn TYPE btcjobcnt
- i_email TYPE ad_smtpadr
- i_delay TYPE i.
- "! Punto de entrada: decide el modo de ejecucion (online / fondo)
- METHODS ejecutar.
- PRIVATE SECTION.
- CONSTANTS:
- c_status_activo TYPE btcstatus VALUE 'R', " Job en ejecucion
- c_report TYPE sy-repid VALUE 'ZAG3_PRG_TOOL_000'. " Nombre del programa/job
- DATA:
- g_job TYPE btcjob, " Nombre del job a monitorizar
- g_jobn TYPE btcjobcnt, " Numero del job a monitorizar
- g_email TYPE ad_smtpadr, " Direccion de correo destino
- g_delay TYPE i, " Retardo (minutos) para reprogramar
- g_sel_jobname TYPE btcjob, " Nombre del job seleccionado (online)
- g_sel_jobn TYPE btcjobcnt. " Numero del job seleccionado (online)
- "! Ejecucion interactiva: lista, selecciona y planifica
- METHODS ejecutar_online.
- "! Ejecucion en fondo: comprueba estado y reprograma o avisa
- METHODS ejecutar_fondo.
- "! Devuelve los jobs activos que cumplen nombre y rango de fechas
- METHODS leer_jobs_activos
- RETURNING VALUE(r_jobs) TYPE tt_jobs.
- "! Muestra el listado en ALV y guarda el job seleccionado
- METHODS mostrar_y_seleccionar
- IMPORTING i_jobs TYPE tt_jobs.
- "! Programa un nuevo job que relanza este programa
- METHODS planificar_job
- IMPORTING i_job TYPE btcjob
- i_jobn TYPE btcjobcnt
- i_fecha TYPE sy-datum
- i_hora TYPE sy-uzeit.
- "! Indica si el job sigue activo
- METHODS job_activo
- IMPORTING i_job TYPE btcjob
- i_jobn TYPE btcjobcnt
- RETURNING VALUE(r_activo) TYPE abap_bool.
- "! Lee los datos de cabecera del job
- METHODS leer_datos_job
- IMPORTING i_job TYPE btcjob
- i_jobn TYPE btcjobcnt
- RETURNING VALUE(r_job) TYPE tbtco.
- "! Envia el correo de finalizacion del job
- METHODS enviar_email
- IMPORTING i_job TYPE tbtco.
- "! Traduce el codigo de estado del job a texto legible
- METHODS texto_estado
- IMPORTING i_status TYPE btcstatus
- RETURNING VALUE(r_texto) TYPE string.
- ENDCLASS.
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- *--[ Implementacion de la clase monitor
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- CLASS lcl_monitor IMPLEMENTATION.
- METHOD constructor.
- g_job = i_job.
- g_jobn = i_jobn.
- g_email = i_email.
- g_delay = i_delay.
- ENDMETHOD.
- METHOD ejecutar.
- "sy-batch = 'X' cuando el programa se ejecuta en fondo
- IF sy-batch = abap_true.
- ejecutar_fondo( ).
- ELSE.
- ejecutar_online( ).
- ENDIF.
- ENDMETHOD.
- METHOD ejecutar_online.
- "1) Obtener los jobs activos que cumplen los filtros
- DATA(lt_jobs) = leer_jobs_activos( ).
- IF lt_jobs IS INITIAL.
- MESSAGE 'No existen jobs activos para el nombre y fechas indicados' TYPE 'S'.
- RETURN.
- ENDIF.
- "2) Mostrar el listado y recoger la seleccion del usuario
- mostrar_y_seleccionar( lt_jobs ).
- IF g_sel_jobn IS INITIAL.
- RETURN. "El usuario no ha seleccionado ningun job
- ENDIF.
- "3) Programar un job que relanza este programa con el job elegido
- " (inicio inmediato)
- planificar_job( i_job = g_sel_jobname
- i_jobn = g_sel_jobn
- i_fecha = sy-datum
- i_hora = sy-uzeit ).
- MESSAGE |Monitorizacion programada para el job { g_job } / { g_sel_jobn }| TYPE 'S'.
- ENDMETHOD.
- METHOD ejecutar_fondo.
- "1) Comprobar si el job sigue activo
- IF job_activo( i_job = g_job i_jobn = g_jobn ) = abap_true.
- "2a) Sigue activo: reprogramar este programa 5 minutos despues
- DATA lv_fecha TYPE sy-datum.
- DATA lv_hora TYPE sy-uzeit.
- DATA lv_stamp TYPE timestamp.
- GET TIME STAMP FIELD lv_stamp.
- "Sumamos el retardo de forma segura (controla el cambio de dia)
- lv_stamp = cl_abap_tstmp=>add( tstmp = lv_stamp
- secs = g_delay * 60 ).
- CONVERT TIME STAMP lv_stamp TIME ZONE sy-zonlo
- INTO DATE lv_fecha TIME lv_hora.
- planificar_job( i_job = g_job
- i_jobn = g_jobn
- i_fecha = lv_fecha
- i_hora = lv_hora ).
- ELSE.
- "2b) Ha finalizado: enviar correo con el estado final
- enviar_email( leer_datos_job( i_job = g_job i_jobn = g_jobn ) ).
- ENDIF.
- ENDMETHOD.
- METHOD leer_jobs_activos.
- "El nombre del job puede contener comodines. Traducimos los
- "comodines habituales de SAP (* y +) a los de SQL (% y _) para
- "poder usar el operador LIKE en la consulta.
- DATA(lv_pattern) = g_job.
- REPLACE ALL OCCURRENCES OF '*' IN lv_pattern WITH '%'.
- REPLACE ALL OCCURRENCES OF '+' IN lv_pattern WITH '_'.
- "Jobs en ejecucion que coinciden con el patron de nombre y el
- "rango de fechas
- SELECT * FROM tbtco
- INTO TABLE @r_jobs
- WHERE jobname LIKE @lv_pattern
- AND status = @c_status_activo
- AND sdldate IN @s_fecha
- ORDER BY sdldate DESCENDING, sdltime DESCENDING.
- ENDMETHOD.
- METHOD mostrar_y_seleccionar.
- DATA(lt_jobs) = i_jobs. "Copia local editable para el ALV
- "Construir el ALV en modo popup
- TRY.
- cl_salv_table=>factory(
- IMPORTING r_salv_table = DATA(lo_alv)
- CHANGING t_table = lt_jobs ).
- CATCH cx_salv_msg INTO DATA(lx_salv).
- MESSAGE lx_salv->get_text( ) TYPE 'S'.
- RETURN.
- ENDTRY.
- "Activar funciones y seleccion de una unica fila
- lo_alv->get_functions( )->set_all( ).
- lo_alv->get_columns( )->set_optimize( ).
- DATA(lo_sel) = lo_alv->get_selections( ).
- lo_sel->set_selection_mode( if_salv_c_selection_mode=>single ).
- lo_alv->set_screen_popup( start_column = 5
- end_column = 120
- start_line = 2
- end_line = 20 ).
- lo_alv->display( ).
- "Recoger la fila seleccionada tras cerrar el popup
- DATA(lt_rows) = lo_sel->get_selected_rows( ).
- IF lt_rows IS INITIAL.
- MESSAGE 'No se ha seleccionado ningun job' TYPE 'S'.
- RETURN.
- ENDIF.
- READ TABLE lt_jobs INTO DATA(ls_job) INDEX lt_rows[ 1 ].
- IF sy-subrc = 0.
- g_sel_jobn = ls_job-jobcount.
- g_sel_jobname = ls_job-jobname.
- ENDIF.
- ENDMETHOD.
- METHOD planificar_job.
- DATA lv_jobcount TYPE btcjobcnt.
- DATA(lv_jobname) = CONV btcjob( c_report ).
- "1) Abrir el job
- CALL FUNCTION 'JOB_OPEN'
- EXPORTING jobname = lv_jobname
- IMPORTING jobcount = lv_jobcount
- EXCEPTIONS cant_create_job = 1
- invalid_job_data = 2
- jobname_missing = 3
- OTHERS = 4.
- IF sy-subrc <> 0.
- MESSAGE 'Error al abrir el nuevo job de monitorizacion' TYPE 'S'.
- RETURN.
- ENDIF.
- "2) Incluir este mismo programa con los parametros de entrada
- SUBMIT (c_report)
- VIA JOB lv_jobname NUMBER lv_jobcount
- WITH p_job = i_job
- WITH p_jobn = i_jobn
- WITH s_fecha IN s_fecha[]
- WITH p_email = g_email
- WITH p_delay = g_delay
- AND RETURN.
- "3) Cerrar el job con la fecha/hora de inicio deseada
- CALL FUNCTION 'JOB_CLOSE'
- EXPORTING jobcount = lv_jobcount
- jobname = lv_jobname
- sdlstrtdt = i_fecha
- sdlstrttm = i_hora
- EXCEPTIONS cant_start_immediate = 1
- invalid_startdate = 2
- jobname_missing = 3
- job_close_failed = 4
- job_nosteps = 5
- job_notex = 6
- lock_failed = 7
- OTHERS = 8.
- IF sy-subrc <> 0.
- MESSAGE 'Error al planificar el nuevo job de monitorizacion' TYPE 'S'.
- ENDIF.
- ENDMETHOD.
- METHOD job_activo.
- SELECT SINGLE status FROM tbtco
- INTO @DATA(lv_status)
- WHERE jobname = @i_job
- AND jobcount = @i_jobn.
- r_activo = xsdbool( sy-subrc = 0 AND lv_status = c_status_activo ).
- ENDMETHOD.
- METHOD leer_datos_job.
- SELECT SINGLE * FROM tbtco
- INTO @r_job
- WHERE jobname = @i_job
- AND jobcount = @i_jobn.
- ENDMETHOD.
- METHOD enviar_email.
- "Determinar la maquina (servidor) donde se ha ejecutado el job
- DATA(lv_maquina) = COND btcsrvname(
- WHEN i_job-execserver IS NOT INITIAL THEN i_job-execserver
- ELSE i_job-reaxserver ).
- "Construir el cuerpo del mensaje
- DATA(lt_body) = VALUE soli_tab(
- ( line = 'El job ha finalizado' )
- ( line = |Estado : { texto_estado( i_job-status ) }| )
- ( line = |Maquina : { lv_maquina }| )
- ( line = |Mandante : { sy-mandt }| )
- ( line = |Inicio : { i_job-strtdate DATE = USER } { i_job-strttime TIME = USER }| )
- ( line = |Fin : { i_job-enddate DATE = USER } { i_job-endtime TIME = USER }| ) ).
- "Enviar el correo mediante la clase estandar del proyecto. Se solicita
- "el envio inmediato (i_immediate = abap_true) para que SOLO este correo
- "salga al instante y no quede en espera en la SOST; el resto de la cola
- "de SAPconnect no se ve afectada.
- DATA(lo_mail) = NEW zini_cl_email( ).
- lo_mail->set_subject( CONV #( i_job-jobname ) ). " Asunto = nombre del job
- lo_mail->set_body( lt_body ).
- lo_mail->set_to( CONV #( g_email ) ).
- lo_mail->set_from( CONV #( 'email@email.com' ) ).
- lo_mail->send_email( i_immediate = abap_true ).
- ENDMETHOD.
- METHOD texto_estado.
- r_texto = SWITCH #( i_status
- WHEN 'R' THEN 'En ejecucion (activo)'
- WHEN 'F' THEN 'Finalizado correctamente'
- WHEN 'A' THEN 'Cancelado'
- WHEN 'S' THEN 'Liberado / Programado'
- WHEN 'P' THEN 'Planificado'
- WHEN 'Y' THEN 'Preparado para ejecucion'
- WHEN 'Z' THEN 'En espera de evento'
- ELSE |Desconocido ({ i_status })| ).
- ENDMETHOD.
- ENDCLASS.
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- *--[ Logica principal
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- START-OF-SELECTION.
- DATA(go_tool) = NEW lcl_monitor( i_job = p_job
- i_jobn = p_jobn
- i_email = p_email
- i_delay = p_delay ).
- go_tool->ejecutar( ).