Limpiar filtro
Artículo
Luis Angel Pérez Ramos · 25 mayo, 2023
Es posible que ya hayáis oído que, a partir de las versiones de IRIS y HealthShare HealthConnect 2023.2, se va a eliminar de la instalación por defecto el Apache Server interno por lo que será necesario contar con un servidor de aplicaciones externo como puede ser Apache Server o NGINX.
En este artículo voy a proceder a realizar la instalación de un HealthShare HealthConnect 2023.1 para que funcione con un Apache Server pre-instalado. Para ello utilizaré una máquina virtual sobre la que he instalado un Ubuntu 22.04.
Instalando Apache Server
Como hemos indicado deberemos instalar previamente nuestro Apache Server y lo haremos siguiendo los pasos que nos indica su propia web
sudo apt update
sudo apt install apache2
Apache Server instalado, prosigamos con la instalación de HealthConnect.
Instalando HealthConnect
Revisemos lo que nos indica la documentación oficial de InterSystems. Si consultáis la documentación nos indica cómo descomprimir el archivo que hemos descargado de WRC. En mi caso tendré que hacer unos pequeños retoques ya que tengo el fuente de HealthConnect en una carpeta compartida con mi máquina virtual.
mkdir /tmp/iriskit
chmod og+rx /tmp/iriskit
umask 022
gunzip -c /mnt/hgfs/shared/HealthConnect-2023.1.0.229.0-lnxubuntu2204x64.tar.gz | ( cd /tmp/iriskit ; tar xf - )
Repasemos que estamos haciendo con estos comandos.
Creamos el directorio donde vamos a descomprimir nuestro fuente de HealthConnect.
Damos permisos de lectura y ejecución del directorio creado tanto al propietario del archivo como a todos los usuarios del grupo del propietario para poder descomprimir el archivo en el directorio indicado.
Damos permisos de lectura, escritura y ejecución sobre todos los archivos y directorios que se vayan a crear.
Descomprimimos el archivo gz y accediendo a la ruta donde lo hemos descomprimido descomprimimos el archivo tar.
Veamos como ha quedado nuestro directorio temporal.
Perfecto, aquí tenemos el código de nuestro HealthShare HealthConnect. Siguiente paso, crear un usuario que será el propietario de la instalación de HealthConnect, lo llamaremos irisusr y a continuación desde nuestro directorio temporal ejecutaremos el comando de instalación:
sudo useradd irisusr
sudo sh irisinstall
Al ejecutar la instalación se nos mostrarán una serie de opciones que deberemos ir configurando con los valores que deseemos. En este caso vamos a realizar una instalación CUSTOM para poder configurar el WebGateway con el servidor web de Apache
Y continuamos...
En este punto deberemos indicar que deseamos configurar el Web Gateway con el Apache Web Server ya desplegado en nuestro servidor y a continuar definir la ruta de instalación del mismo.
Configuración de Apache Server
La instalación configurará automáticamente el Web Gateway para funcionar con el Apache Server y nuestra instancia APACHETEST de HealthConnect. En este ejemplo, al ser Ubuntu 22.0.4, el fichero de configuración de Apache se encuentran en la ruta /etc/apache2/apache2.conf , para otras distribuciones de Linux podéis consultar la documentación.
Si abrimos el archivo apache2.conf y bajamos hasta el final del mismo podremos comprobar los cambios introducidos por la instalación de Web Gateway:
Esta configuración está redirigiendo todas las llamadas que reciba el puerto 80 (puerto en el que Apache Server escucha por defecto) con la ruta /csp a nuestro Web Gateway, el cual a su vez enviará la llamada a nuestra instancia de HealthConnect. Por defecto el parámetro CSPFileTypes está configurada únicamente para redireccionar los archivos de tipo "csp cls zen cxw", para poder trabajar con el portal de gestión sin inconveniente lo hemos modificado para aceptar todos los tipos "*". El cambio de CSPFileTypes exige reiniciar Apache Server.
Acceso al portal de gestión
Muy bien, tenemos nuestro Apache escuchando en el puerto 80, nuestro Web Gateway configurado y la instancia de HealthConnect arrancada. Probemos el acceso al portal de gestión utilizando el puerto 80 de Apache.
En mi caso la URL de acceso será http://192.168.31.214/csp/sys/%25CSP.Portal.Home.zen , al ser el puerto 80 el puerto por defecto no será necesario incluirlo en la URL.
Aquí tenemos nuestro portal de gestión plenamente operativo. Introduzcamos el usuario y la contraseña que definimos durante la instalación y abramos la configuración del Web Gateway desde la opción System Administrator --> Configuration --> Web Gateway Management. Esta vez el usuario de acceso será CSPSystem. Recordemos que el Web Gateway está configurado para funcionar con el Apache Web Server que hemos instalado previamente.
Accedamos a la opción de Server Access para comprobar la configuración de nuestra instancia de HealthConnect en el Web Gateway:
Ahí tenemos nuestra instancia. Comprobemos como el Web Gateway gestiona las llamadas recibidas desde el Apache Server abriendo la opción de Application Access.
Comprobemos que hace con las URL que empiecen por /csp
Ahí está nuestra instancia configurada por defecto para recibir las llamadas que lleguen a nuestro Apache Server.
Ya tendríamos nuestra instancia de HealthShare HealthConnect configurada para trabajar con un Apache Server externo y el Web Gateway. Si tenéis cualquier pregunta o sugerencia no dudéis en enviarnos un comentario.
Artículo
Bernardo Linarez · 1 jul, 2019
Como todos sabemos, IRIS Database / Caché es un motor de base de datos que efectúa muchas tareas dentro de sí misma. Sin embargo, ¿qué puede hacer cuando necesita tener acceso a una base de datos externa? Una opción es utilizar el SQL Gateway en Caché mediante un conector JDBC. En este artículo, mi objetivo es responder las siguientes preguntas con el fin de ayudarle a que se familiarice con la tecnología y que resuelva algunos de los más problemas comunes.Resumen general¿Cuáles son los parámetros de conexión que necesita para conectarse a una base de datos remota?¿Cuál es la diferencia de JDBC Gateway en comparación con el servicio que ofrece Java Gateway en la capa de interoperabilidad?¿Cuáles son las herramientas y métodos que están disponibles para resolver problemas?¿Cuáles son los tipos de problemas más comunes y los enfoques para resolverlos?Antes de profundizar en estas preguntas, hablemos rápidamente de la arquitectura de la Conectividad con las bases de datos de Java (JDBC) en SQL Gateway. Para que esto sea más sencillo, puede pensar en la arquitectura como una aplicación en IRIS Database / Cache realiza una conexión TCP con alguno de los procesos de Java, dicho proceso se llama Puerta de enlace. Entonces, el proceso Puerta de enlace se conecta con una base de datos remota, como Caché, Oracle o SQL Server, mediante el controlador que se especificó para esa base de datos. Para obtener más información sobre la arquitectura de SQL Gateway, consulte los documentos de apoyo sobre el uso de SQL Gateway en Caché.Parámetros de conexiónCuando se conecte con una base de datos remota, debe proporcionar los siguientes parámetros:Nombre de usuarioContraseñaNombre del controladorURLRuta para la claseCómo conectarse con una base de datos de CachéPor ejemplo, si necesita conectarse con una instancia de Caché, que utilice SQL Gateway mediante la JDBC, necesita navegar hacia [System Administration] -> [Configuration] -> [Connectivity] -> [SQL Gateway Connections] en el portal de Administración del sistema (SMP). A continuación, haga clic en "Crear una nueva conexión" y especifique "JDBC" como el tipo de conexión.Cuando se conecte a un sistema IRIS Database / Caché, el nombre del controlador siempre debe ser com.intersys.jdbc.CacheDriver, como se muestra en la captura de pantalla. Si se conecta con una base de datos de terceros, entonces deberá utilizar un nombre diferente para el controlador (consulte la sección Cómo conectarse con las bases de datos de terceros que se encuentra más abajo).Cuando se conecte con las bases de datos de Caché, no es necesario que especifique una ruta para la clase, porque el archivo JAR se carga automáticamente.El parámetro URL también variará dependiendo de la base de datos a la que se esté conectando. Para las bases de datos de Caché, debe utilizar una URL en el formulario jdbc:Cache://[server_address]:[superserver_port]/[namespace]Cómo conectarse con las bases de datos de tercerosUna base de datos común de terceros es Oracle. A continuación, se muestra un ejemplo de su configuración.Como puede ver, el nombre del controlador y la URL tienen patrones diferentes a los que utilizamos para la conexión anterior. Además, especifiqué una ruta para la clase en este ejemplo, porque necesito utilizar el controlador de Oracle para conectarme a su base de datos.Como puede imaginarse, SQL Server utiliza diferentes URL y patrones para el nombre del controlador.Puede probar si los valores son válidos al hacer clic en el botón "Test Connection". Para establecer la conexión, haga clic en "Save".JDBC Gateway contra el Servicio empresarial de Java GatewayEn primer lugar, los servicios de JDBC Gateway y Java Gateway son completamente independientes uno del otro. JDBC Gateway puede utilizarse en todos los sistemas que se basan en Caché, mientras que el Servicio empresarial de Java Gateway únicamente existe como parte de la IRIS Interoperabilidad / Ensemble. Además, el servicio de Java Gateway utiliza un proceso diferente en comparación al que utiliza JDBC Gateway. Para obtener más información sobre el Servicio empresarial de Java Gateway, consulte El Servicio empresarial de Java Gateway.Herramientas y métodosA continuación, se muestran 5 herramientas y métodos comunes que se utilizan para resolver problemas con JDBC SQL Gateway. Mi intención primero es discutir sobre estas herramientas y mostrarle algunos ejemplos de cuándo se utilizan en la siguiente sección.1. RegistrosA. El registro del controlador contra el registro de la Puerta de enlaceCuando se utiliza JDBC Gateway, el registro correspondiente es el registro JDBC de SQL Gateway. Como comentamos anteriormente, el JDBC Gateway se utiliza cuando Caché necesita acceder a las bases de datos externas, lo cual significa que Caché es el cliente. El registro del controlador, sin embargo, corresponde al que utiliza el controlador JDBC de InterSystems para acceder a una base de datos de Caché desde una aplicación externa, lo que significa que Caché es el servidor. Si tiene una conexión desde una base de datos de Caché con otra base de datos de Caché, ambos tipos de registro pueden ser útiles.En nuestros documentos de apoyo, la sección sobre la habilitación del registro del controlador se denomina "Habilitación del registro para la JDBC", y la sección sobre la habilitación del registro para la puerta de enlace se denomina "Habilitación del registro para la JDBC con SQL Gateway".Aunque en ambos registros se incluye la palabra "JDBC", estos son completamente independientes. El objetivo de este artículo es conocer más sobre JDBC Gateway, de modo que analizaré con más detalle el registro para la puerta de enlace. Para obtener más información sobre el registro del controlador, consulte la Habilitación del registro del controlador .B. Habilitación del registro para la Puerta de enlaceSi utiliza el SQL Gateway en Caché mediante la JDBC, entonces debe hacer lo siguiente para habilitar el registro: en el Portal de administración, vaya a [System Administration] > [Configuration] > [Connectivity] > [JDBC Gateway Settings]. Especifique un valor para el registro de JDBC Gateway. Esta debe ser la ruta completa y el nombre de un archivo de registro (por ejemplo, /tmp/jdbcGateway.log). Sino existe, el archivo se creará automáticamente pero el directorio no existirá. Caché iniciará con el registro de la JDBC con SQL Gateway por usted.Si utiliza el Servicio empresarial de Java Gateway en Ensemble, consulte Habilitación del registro para Java Gateway para obtener más información sobre cómo habilitar el registro.C. Análisis del registro para la Puerta de enlaceAhora que recopiló un registro para la puerta de enlace, quizás se pregunte: ¿cuál es la estructura del registro y cómo puedo leerlo? ¡Excelentes preguntas! Aquí le proporcionaré la información básica para que pueda comenzar. Desafortunadamente, no siempre es posible interpretar completamente el registro sin acceder al código fuente, así que para este tipo de situaciones complejas, ¡no dude en ponerse en contacto con el Centro Mundial de Respuesta de Intersystems (WRC)!Para desmitificar la estructura del registro, recuerde que siempre son segmentos grandes de datos seguidos por una descripción de lo que hacen. Por ejemplo, observe esta imagen en la que se resalta a algunos elementos básicos de sintaxis:Para entender lo que el término Received significa aquí, es necesario recordar que en el registro de la puerta de enlace se documentan las interacciones entre la puerta de enlace y el flujo descendente de la base de datos. Por lo tanto, el término Received significa que la información proveniente de IRIS Database/Caché/Ensemble se recibió en la puerta de enlace. En el ejemplo anterior, la puerta de enlace recibió el texto de una consulta para SELECT . El significado de los diferentes valores de msgId se encuentran en el código interno. El 33 que vemos aquí significa "Preparar una sentencia".El registro por sí mismo también proporciona información sobre el controlador, la cual se puede verificar cuando se depuran los errores. Por ejemplo:Como podemos ver, el Driver Name es com.intersys.jdbc.CacheDriver, que es el nombre del controlador que se utilizó para conectarse con el proceso para la puerta de enlace. El Jar File Name es cachejdbc.jar, que es el nombre del archivo jar que se encuentra en <cache_install_directory>cache_install_directory>\lib\.2. Cómo encontrar el proceso para la puerta de enlacePara encontrar el proceso para la puerta de enlace, puede ejecutar el comando ps . Por ejemplo:ps -ef | grep javaCon el comando ps se despliega la información sobre el proceso de Java, que incluye el número de puerto, el archivo jar, el archivo de registro, el ID del proceso Java y el comando que comenzó el proceso de Java.Este es un ejemplo del resultado del comando:mlimbpr15:~ mli$ ps -ef | grep java 17182 45402 26852 0 12:12PM ?? 0:00.00 sh -c java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC.log 2>&1 17182 45403 45402 0 12:12PM ?? 0:00.22 /usr/bin/java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC.log 502 45412 45365 0 12:12PM ttys000 0:00.00 grep javaEn Windows, puede verificar el administrador de tareas para encontrar más información sobre el proceso para la puerta de enlace.3. Cómo iniciar y detener la puerta de enlaceExisten dos formas para iniciar y detener la puerta de enlace:Mediante el SMPUtilizando el TerminalA. Mediante el SMPPuede iniciar y detener la puerta de enlace en el SMP al ingresar en [System Administration] -> [Configuration] -> [Connectivity] -> [JDBC Gateway Server].B. Utilizando el TerminalEn equipos que cuentan con el sistema operativo Unix, también puede iniciar la puerta de enlace desde el terminal. Como comentamos en la sección anterior, el resultado de ps -ef | grep java contiene el comando que inició el proceso para Java, que en el ejemplo anterior es:java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC.logPara detener la puerta de enlace desde el terminal, puede finalizar el proceso. El ID del proceso en Java es el segundo número de la línea que contiene el comando anterior, que en el ejemplo anterior es 45402. Por lo tanto, para detener la puerta de enlace, puede ejecutar:kill 454024. Cómo escribir un programa en JavaPara ejecutar un programa en Java debe conectarse con una base de datos de flujo descendente, ya que es una excelente manera de probar la conexión, verificar las consultas y aislar la causa de un problema determinado. Adjunté un ejemplo de un programa de Java, el cual establece una conexión con SQL Server e imprime una lista de todas las tablas. En la siguiente sección explicaré por qué esto puede ser útil.import java.sql.*; import java.sql.Date; import java.util.*; import java.lang.reflect.Method; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.math.BigDecimal; import javax.sql.*; // Autor: Vicky Li// Este programa establece una conexión con SQL Server y recupera todas las tablas. El resultado es una lista de tablas. public class TestConnection { public static void main(String[] args) { try { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); //reemplace la URL, el nombre de usuario y la contraseña con los parámetros correctos Connection conn = DriverManager.getConnection(url,username,password); System.out.println("connected"); DatabaseMetaData meta = conn.getMetaData(); ResultSet res = meta.getTables(null, null, null, new String[] {"TABLE"}); System.out.println("List of tables: "); while (res.next()) { System.out.println( " " + res.getString("TABLE_CAT") + ", " + res.getString("TABLE_SCHEM") + ", " + res.getString("TABLE_NAME") + ", " + res.getString("TABLE_TYPE") ); } catch (Exception e) { e.printStackTrace(); }Para ejecutar este programa de Java (o cualquier otro programa de Java), primero debe compilar el archivo .java , que en nuestro caso se llama TestConnection.java. A continuación, se generará un archivo nuevo en la misma ubicación, el cual podrá ejecutar con el siguiente comando en un sistema UNIX:java -cp "<path to driver>/sqljdbc4.jar:lib/*:." TestConnectionEn Windows, puede ejecutar el siguiente comando:java -cp "<path to driver>/sqljdbc4.jar;lib/*;." TestConnection5. Cómo realizar un seguimiento del jstackComo su nombre lo indica, jstack imprime un registro de seguimiento (stack traces) de Java en los subprocesos de Java. Esta herramienta puede ser útil cuando se necesita comprender mejor lo que está haciendo alguno de los procesos de Java. Por ejemplo, si observa que el proceso para la puerta de enlace reproduce un mensaje determinado en su registro, es posible que desee recopilar un seguimiento del jstack. Quiero señalar que jstack es una herramienta de bajo nivel que solo debería utilizarse cuando otros métodos, como el análisis del registro para la puerta de enlace, no resuelven el problema.Antes de que recopile un seguimiento del jstack, debe asegurarse de que el Java Development Kit (JDK) está instalado. Este es el comando para recopilar un seguimiento del jstack:jstack -F <pid> > /<path to file>/jstack.txtdonde pid es el ID del proceso para la puerta de enlace, el cual puede obtenerse cuando se ejecuta el comando ps, por ejemplo, en ps -ef | grep java. Para obtener más información sobre cómo encontrar el pid, consulte de nuevo Cómo iniciar y detener la puerta de enlace.Ahora bien, aquí puede consultar algunas consideraciones especiales para los equipos que cuentan con Red Hat. En el pasado, hubo problemas para adjuntar el jstack en el proceso de JDBC Gateway (al igual que en el proceso del Servicio empresarial de Java Gateway que comenzó en Ensemble) con algunas versiones de Red Hat, por lo que la mejor manera para recopilar un seguimiento del jstack en Red Hat es iniciar el proceso para la puerta de enlace de forma manual. Para obtener las instrucciones, consulte la sección Cómo recopilar un seguimiento del jstack en Red Hat.Los tipos de problemas más comunes y los enfoques para resolverlos1. Problema: Java no se instaló correctamenteEn esta situación, verifique la versión de Java y las variables de entorno.Para verificar la versión de Java, puede ejecutar la siguiente línea de comando desde un terminal:java -versionSi recibe el error java: Command not found, entonces el proceso de Cache no puede encontrar la ubicación de los ejecutables de Java. Esto normalmente se puede arreglar con la instalación de los ejecutables de Java en PATH. Si tiene problemas para realizar esto, no dude en ponerse en contacto con el Centro Mundial de Respuesta de Intersystems (WRC).2. Problema: Hubo un fallo en la conexiónUn buen método para diagnosticar los fallos en la conexión es verificar si el proceso para la puerta de enlace se inicia correctamente. Puede hacerlo, ya sea, al verificar el registro para la puerta de enlace o el proceso para la puerta de enlace. En las versiones modernas, también puede ir al SMP y consultar [System Administration] -> [Configuration] -> [Connectivity] -> [JDBC Gateway Server], para comprobar si en la página se muestra el mensaje "JDBC Gateway está en ejecución".Si el proceso para la puerta de enlace no está en ejecución, es posible que Java no se instaló correctamente o que utiliza el puerto equivocado, si el proceso para la puerta de enlace está en ejecución, entonces es probable que los parámetros de conexión no sean los correctos.Si ocurre la primera situación, consulte la sección anterior y verifique nuevamente el número del puerto. Aquí analizaré con más detalle esta última situación.Es responsabilidad del cliente utilizar los parámetros de conexión correctos:nombre de usuariocontraseñanombre del controladorURLruta de la clasePuede verificar si tiene los parámetros correctos mediante cualquiera de las siguientes tres formas:Utilice el botón "Test Connection" después de seleccionar un nombre para la conexión en [System Administration] -> [Configuration] -> [Connectivity] -> [SQL Gateway Connections].Nota: en los sistemas modernos, el procedimiento "Test Connection" proporciona mensajes de error que son útiles, en los sistemas más antiguos es necesario el registro de JDBC Gateway para encontrar más información sobre el fallo.Ejecute la siguiente línea de comandos desde un terminal de Caché para probar la conexión: d $SYSTEM.SQLGateway.TestConnection(<connection name>)Ejecute un programa de Java para establecer una conexión. El programa que escriba puede ser similar al ejemplo que discutimos anteriormente.3. Problema: falta de correspondencia entre la forma en que Caché interpreta al JDBC y la forma en que la base de datos remota interpreta al JDBC, como en:los problemas con el tipo de datosel procedimiento para almacenar con parámetros de salidalas transmisionesPara esta categoría, con frecuencia es más útil trabajar con ayuda del Centro Mundial de Respuesta de Intersystems (WRC). Este es el procedimiento que realizamos con frecuencia para determinar si el problema está dentro de nuestro código interno o en la base de datos remota (o con el controlador):Se revisan los registros y se analiza lo que se envióSe reproduce el problema que se encuentra fuera del Caché al escribir un programa en Java.Notas al pie de páginaEl Bussiness Service JavaGatewayEl nombre de la clase del Servicio empresarial en IRIS Interoperabilidad/Ensemble es EnsLib.JavaGateway.Service, y la clase del adaptador es EnsLib.JavaGateway.ServiceAdapter. En la sesión de Ensemble primero se crea una conexión con Java Gateway Server, el cual es un proceso de Java. La arquitectura es similar a la que se encuentra en la JDBC con SQL Gateway, excepto que el proceso de Java lo administra la Actividad empresarial. Para obtener más detalles, consulte los documentos de apoyo.Cómo habilitar el registro del controladorPara habilitar el registro del controlador, debe añadir un nombre de archivo para el registro al final de la cadena de conexión de la JDBC. Por ejemplo, si la cadena de conexión original tiene el siguiente aspecto:jdbc:Cache://127.0.0.1:1972/USERPara habilitar el registro, agregue un archivo (jdbc.log) al final de la cadena de conexión, de modo que se vea como lo siguiente:jdbc:Cache://127.0.0.1:1972/USER/jdbc.logEl archivo de registro se guardará en el directorio de trabajo de la aplicación Java.Habilitación del registro de Java Gateway en EnsembleSi utiliza el Business Service (BS) de Java Gateway en Ensemble para ingresar a otra base de datos, entonces para habilitar el registro debe especificar la ruta y el nombre de un archivo de registro (por ejemplo, /tmp/javaGateway.log) en el campo "Log File" del servicio Java Gateway. Tenga en cuenta que la ruta ya debe existir.Recuerde, la conexión de Java Gateway que se utiliza en la producción de Ensemble es independiente de las conexiones que utilizan las tablas vinculadas u otras producciones. Por lo tanto, si utiliza Ensemble, debe recopilar el registro en el servicio de Java Gateway. El código que inicia el servicio Java Gateway utiliza el parámetro "Log File" en Ensemble, y no utiliza la configuración del Caché SQL Gateway en el SMP como se describió anteriormente.Cómo recopilar un seguimiento del jstack en Red HatLa clave aquí es iniciar el proceso para la puerta de enlace manualmente, y el comando para iniciar la puerta de enlace se puede obtener con la ejecución de ps -ef | grep java. Más adelante se muestran todos los pasos que deben seguirse cuando se recopila un seguimiento del jstack en Red Hat para ejecutar el JDBC Gateway o el Servicio empresarial de Java Gateway.Asegúrese de que el JDK está instalado.En un terminal, ejecute el comando ps -ef | grep java. Obtenga las siguientes dos piezas de información a partir del resultado:Copie el comando que inició la puerta de enlace. Debería verse algo como esto: java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC2.logObtenga el ID del proceso de Java (pid), es decir, el segundo número de la línea que contiene el comando anteriorFinalice el proceso con kill <pid>.Ejecute el comando que copió del Paso 2.a. para comenzar un proceso para la puerta de enlace de forma manual.Eche un vistazo al registro para la puerta de enlace (en nuestro ejemplo, se encuentra en /Applications/Cache20151/mgr/JDBC2.log) y asegúrese de ver una entrada parecida a >> LOAD_JAVA_CLASS: com.intersys.jdbc.CacheDriver. Este paso es solo para verificar que la llamada para la puerta de enlace se realizó correctamente.En un terminal nuevo, ejecute el comando ps -ef | grep java para obtener el pid del proceso para la puerta de enlace.Recopile un seguimiento del jstack: jstack -F <pid> > /tmp/jstack.txt
Artículo
Joel Espinoza · 27 feb, 2020
Hola Comunidad:
Esta publicación está dedicada a la tarea de supervisar una instancia de Caché usando SNMP. Algunos usuarios de Caché probablemente ya lo hacen de una u otra forma. Hace ya mucho que el paquete de Caché estándar soporta la supervisión por SNMP, pero no todos los parámetros necesarios vienen "listos para usar". Por ejemplo, sería bueno poder supervisar el número de sesiones CSP, obtener información detallada sobre el uso de la licencia, KPI particulares del sistema en uso, etc. Después de leer este artículo, sabrás cómo agregar tus parámetros a la supervisión de Caché mediante SNMP.
Lo que ya tenemos
Es posible supervisar Caché con SNMP. En los archivos de <Install_dir>/SNMP/ puedes encontrar una lista completa de todo lo soportado. Aquí encontrarás 2 archivos: ISC-CACHE.mib y ISC-ENSEMBLE.mib. Nos interesa el archivo destinado a Caché — ISC-CACHE.mib. En particular, quisiéramos saber qué información podemos obtener sobre licencias y sesiones. La tabla contiene los OID correspondientes, siempre que la jerarquía comience desde la raíz de InterSystems: 1.3.6.1.4.1.1656
OID
Nombre
Descripción
Tipo de dato
.1.1.1.1.10
cacheSysLicenseUsed
El número actual de licencias en uso en esta instancia de Caché
ENTERO
.1.1.1.1.11
cacheSysLicenseHigh
La cota máxima para las licencias usadas en esta instancia de Caché
ENTERO
.1.2.16
cacheLicenseExceed
Una solicitud de licencia ha superado las licencias disponibles o permitidas
Mensaje trap
.1.1.1.1.6
cacheSysCurUser
Número actual de usuarios en esta instancia de Caché
ENTERO
Al paquete le faltan muchos parámetros importantes, como el número de sesiones CSP, información de licencia y, por supuesto, no tiene KPI específicos por aplicación.
Esto es un ejemplo de lo que nos gustaría saber:
El número de usuarios CSP
Limitations of our license in terms of the user count;
License expiry date.
Podemos añadir también unos pocos parámetros para el análisis de desempeño. Los parámetros en sí están en el paquete, pero queremos conocer su incremento por minuto. Por ejemplo:
El aumento del número de referencias “globales” por minuto
La cantidad de comandos de ejecución por minuto
La cantidad de llamadas de rutina por minuto
Cómo agregar “tus” parámetros
Puedes consultar el documento “Monitoring Caché using SNMP”.
La versión de Caché de nuestra instancia de prueba (CACHE2016) es 2016.2.0.721.0. El sistema operativo es Linux Fedora 24 (Workstation Edition). La instalación de Caché en Linux OS se describe al detalle aquí.
Este es nuestro plan:
Crear una clase para recolectar métricas
Registrar y activar una nueva clase en Caché usando ^%SYSMONMGR
Crear una MIB de usuario usando métodos de la clase MonitorTools.SNMP. Usaremos 99990 como PEN (Private Enterprise Number) temporal, pero necesitaremos registrarnos con IANA después. Este proceso es gratuito, lleva una semana o dos y requiere un intercambio de correos electrónicos acerca de “¿Para qué necesita su propio PEN?”
Comenzar un servicio de supervisión con un subagente de Caché conectado
Usar snmpwalk para asegurarse de tener acceso a todos los OID recién creados
Agregar los OID a un sistema de supervisión de otro proveedor. Usemos por ejemplo Zabbix. La documentación de Zabbix está aquí. Asegurémonos de que la supervisión esté funcionando
Agregar el inicio de la supervisión del sistema en el namespace TEST a la lista de inicio del sistema
Sigamos ahora el plan, punto por punto:
1. Crear una clase para recolectar métricas
La clase de recolección de métricas extiende %Monitor.Adaptor. En el terminal, cambiamos al namespace %SYS y exportamos la clase oculta Monitor.Sample:
%SYS>do $system.OBJ.Export("Monitor.Sample.cls","/tmp/Monitor_Sample.xml")
Exporting to XML started on 02/07/2017 21:39:56
Exporting class: Monitor.Sample
Export finished successfully.
Vamos a asumir que el namespace TEST es nuestra área de trabajo. Vamos a pasar a ella e importar aquí la clase Monitor.Sample. Ahora crearemos una clase que describa la implementación de un mecanismo de supervisión para las 6 métricas descritas en la sección "Lo que ya tenemos".
Class monitoring.snmp.Metrics Extends %Monitor.Adaptor
{
/// Give the application a name. This allows you to group different
/// classes together under the same application level in the SNMP MIB.
/// The default is the same as the Package name.
Parameter APPLICATION = "Monitoring";
/// CSP sessions count
Property Sessions As %Monitor.Integer;
/// License user limit
Property KeyLicenseUnits As %Monitor.Integer;
/// License key expiration date
Property KeyExpirationDate As %Monitor.String;
/// Global references speed
Property GloRefSpeed As %Monitor.Integer;
/// Number of commands executed
Property ExecutedSpeed As %Monitor.Integer;
/// Number of routine loads/save
Property RoutineLoadSpeed As %Monitor.Integer;
/// The method is REQUIRED. It is where the Application Monitor
/// calls to collect data samples, which then get picked up by the
/// ^SNMP server process when requested.
Method GetSample() As %Status
{
set ..Sessions = ..getSessions()
set ..KeyLicenseUnits = ..getKeyLicenseUnits()
set ..KeyExpirationDate = ..getKeyExpirationDate()
set perfList = ..getPerformance()
set ..GloRefSpeed = $listget(perfList,1)
set ..ExecutedSpeed = $listget(perfList,2)
set ..RoutineLoadSpeed = $listget(perfList,3) quit $$$OK
}
/// Get CSP sessions count
Method getSessions() As %Integer
{
// This method will only work if we don't use WebAddon:
// quit $system.License.CSPUsers()
//
// This will work even if we use WebAddon:
set csp = ""
try {
set cn = $NAMESPACE
znspace "%SYS"
set db = ##class(SYS.Stats.Dashboard).Sample()
set csp = db.CSPSessions
znspace cn
} catch e {
set csp = "0"
}
quit csp
}
/// Get license user's power
Method getKeyLicenseUnits() As %Integer
{
quit $system.License.KeyLicenseUnits()
}
/// Get license expiration date in human-readable format
Method getKeyExpirationDate() As %String
{
quit $zdate($system.License.KeyExpirationDate(),3)
}
/// Get performance metrics (gloref, rourines etc.)
Method getPerformance(param As %String) As %Integer
{
set cn = $NAMESPACE
znspace "%SYS"
set m = ##class(SYS.Monitor.SystemSensors).%New()
do m.GetSensors()
znspace cn
quit $listbuild(m.SensorReading("GlobalRefsPerMin"),
m.SensorReading("RoutineCommandsPerMin"),
m.SensorReading("RoutineLoadsPerMin"))
}
}
Asegúrate de que el método GetSample() realmente recupere los datos necesarios:
TEST>set metrics = ##class(monitoring.snmp.Metrics).%New()
TEST>write metrics.GetSample()
1
TEST>zwrite metrics
metrics=<OBJECT REFERENCE>[2@monitoring.snmp.Metrics]
+----------------- general information ---------------
| oref value: 2
| class name: monitoring.snmp.Metrics
| reference count: 2
+----------------- attribute values ------------------
| ExecutedSpeed = 431584
| GloRefSpeed = 881
| KeyExpirationDate = "2017-02-28"
| KeyLicenseUnits = 100
| RoutineLoadSpeed = 0
| Sessions = 1
+-----------------------------------------------------
2. Registrar y activar la nueva clase en Caché usando ^%SYSMONMGR
Abre el terminal y pasa al namespace TEST:
# csession cache2016 -U test
Node: server, Instance: CACHE2016
TEST>do ^%SYSMONMGR
1. Select item 5, Manage Application Monitor. 2. Select item 2, Manage Monitor Classes. 3. Select item 3, Register Monitor System Classes.
Exporting to XML started on 02/09/2017 11:22:57
Exporting class: Monitor.Sample
Export finished successfully.
Load started on 02/09/2017 11:22:57
Loading file /opt/intersystems/cache2016/mgr/Temp/Mb7nvq5xuovdHQ.stream as xml
Imported class: Monitor.Sample
Compiling class Monitor.Sample
Compiling table Monitor.Sample
Compiling routine Monitor.Sample.1
Load finished successfully. 4. Select item 1, Activate/Deactivate Monitor Class
Class??
Num MetricsClassName Activated
1) %Monitor.System.AuditCount N
…
15) monitoring.snmp.Metrics N
Class? 15 monitoring.snmp.Metrics
Activate class? Yes => Yes 5. Select item 6, Exit 6. Select item 6 again, Exit 7. Select item 1, Start/Stop System Monitor 8. Select item 2, Stop System Monitor
Stopping System Monitor… System Monitor not running! 9. Select item 1, Start System Monitor
Starting System Monitor… System Monitor started 10. Select item 3, Exit 11. Select item 4, View System Monitor State
Component State
System Monitor OK
%SYS.Monitor.AppMonSensor OK 12. Select item 7, Exit
3. Crear una MIB de usuario
Para crear una MIB de usuario, se usan métodos de clase de MonitorTools.SNMP. Para este ejemplo, usaremos un PEN (Private Enterprise Number) falso, 99990, pero será necesario registrar el PEN con IANA después. Aquí puedes ver los números registrados. Por ejemplo, el PEN de InterSystems es 16563.
16563
InterSystems
Robert Davis
rdavis&intersystems.com
Usaremos la clase MonitorTools.SNMP y su método CreateMIB() para crear un archivo MIB. Este método lleva 10 argumentos:
Nombre y tipo de argumento
Descripción
Valor
AppName As %String
nombre de la aplicación
Valor del parámetro APPLICATION de la clase metrics.snmp.Metrics — Monitoring
Namespace As %String
nuestro namespace
TEST
EntID As %Integer
PEN de la empresa
99990 (ficticio)
AppID As %Integer
OID de aplicación dentro de la empresa
42
Company As %String
nombre de la empresa (mayúsculas)
ficticio
Prefix As %String
prefijo de todos los objetos SNMP que creemos
ficticio
CompanyShort As %String
prefijo corto de la empresa (mayúsculas)
fict
MIBname As %String
nombre del archivo MIB
ISC-TEST
Contact As %String
información de contacto (en particular, dirección)
Dejemos el valor predeterminado: Earth, Russia, Somewhere in the forests, Subject: ISC-TEST.mib
List As %Boolean
equivalente a verbose. Mostrar progreso de tarea para el archivo MIB
1
Ahora llega la creación del archivo MIB:
%SYS>d ##class(MonitorTools.SNMP).CreateMIB("Monitoring","TEST",99990,42,"fiction","fict","fiction","ISC-TEST",,1)
Create SNMP structure for Application - Monitoring
Group - Metrics
ExecutedSpeed = Integer
GloRefSpeed = Integer
KeyExpirationDate = String
KeyLicenseUnits = Integer
RoutineLoadSpeed = Integer
Sessions = Integer Create MIB file for Monitoring
Generate table Metrics
Add object ExecutedSpeed, Type = Integer
Add object GloRefSpeed, Type = Integer
Add object KeyExpirationDate, Type = String
Add object KeyLicenseUnits, Type = Integer
Add object RoutineLoadSpeed, Type = Integer
Add object Sessions, Type = Integer
MIB done.
Ahora hay una nueva MIB ISC-TEST.mib en la carpeta <Install_dir>/mgr/TEST.
4. Comenzar el servicio de supervisión con el subagente de Caché conectado
Abramos System Administration -> Security -> Services -> %Service_Monitor (click) -> Service Enabled (check).
También especificaremos que queremos iniciar el subagente SNMP cuando se inicie Caché (clic en Configure Monitor Settings):
En Linux usamos el paquete net-snmp para supervisión SNMP. Así que lo instalamos, lo configuramos para usar con subagentes y especificamos el puerto 705 como predeterminado para que el agente maestro hable con los subagentes.
# grep -i agentx /etc/services
agentx 705/tcp # AgentX
agentx 705/udp # AgentX
Puede ver un breve artículo sobre el archivo de configuración snmpd.conf que complementa al manual en cyberciti. Aquí están los ajustes finales
# yum install net-snmp
# grep '^[^#]' /etc/snmp/snmpd.conf
master agentx
agentXSocket TCP:localhost:705
com2sec local localhost public
group MyRWGroup v1 local
group MyRWGroup v2c local
group MyRWGroup usm local
view all included .1 80
view system included .iso.org.dod
access MyROGroup "" any noauth exact all none none
access MyRWGroup "" any noauth exact all all none
syslocation server (edit /etc/snmp/snmpd.conf)
syscontact Root <root@localhost> (configure /etc/snmp/snmp.local.conf)
dontLogTCPWrappersConnects yes
Reiniciamos los daemons snmpd y snmptrapd en Linux. Después, iniciaremos el servicio SNMP para activar el subagente de Caché SNMP:
%SYS>do start^SNMP %SYS>; Check SNMP subagent status %SYS>zwrite ^SYS("MONITOR")
^SYS("MONITOR","SNMP")="RUN"
^SYS("MONITOR","SNMP","NAMESPACE")="%SYS"
^SYS("MONITOR","SNMP","PID")=5516
^SYS("MONITOR","SNMP","PORT")=705
^SYS("MONITOR","SNMP","STARTUP")="SNMP agent started on port 705, timeout=20, winflag=0, Debug=0"
^SYS("MONITOR","SNMP","STATE")="Terminated - 01/27/2017 04:15:01.2833PM"
^SYS("MONITOR","SNMP","WINSTART")=0
5. Verifique que solo estén disponibles sus propios OID nuevos
Para hacer esto, use snmpwalk. Mostraremos el OID que indica el número de sesiones CSP:
# snmpwalk -On -v 2c -c public localhost 1.3.6.1.4.1.99990
# snmpwalk -On -v 2c -c public localhost 1.3.6.1.4.1.99990
.1.3.6.1.4.1.99990.42.1.1.1.1.9.67.65.67.72.69.50.48.49.54 = INTEGER: 559851
.1.3.6.1.4.1.99990.42.1.1.1.2.9.67.65.67.72.69.50.48.49.54 = INTEGER: 973
.1.3.6.1.4.1.99990.42.1.1.1.3.9.67.65.67.72.69.50.48.49.54 = STRING: "2017-02-28"
.1.3.6.1.4.1.99990.42.1.1.1.4.9.67.65.67.72.69.50.48.49.54 = INTEGER: 100
.1.3.6.1.4.1.99990.42.1.1.1.5.9.67.65.67.72.69.50.48.49.54 = INTEGER: 0
.1.3.6.1.4.1.99990.42.1.1.1.6.9.67.65.67.72.69.50.48.49.54 = INTEGER: 2 # If you get such result
# .1.3.6.1.4.1.99990 = No Such Object available on this agent at this OID
# try to restart SNMP subagent in Caché in this way:
# do stop^SNMP
# do start^SNMP
El archivo ISC-TEST.mib contiene la secuencia de nuestros OID’s:
FictMetricsR ::=
SEQUENCE {
fictExecutedSpeed Integer32,
fictGloRefSpeed Integer32,
fictKeyExpirationDate DisplayString,
fictKeyLicenseUnits Integer32,
fictRoutineLoadSpeed Integer32,
fictSessions Integer32
}
Por tanto, el número de sesiones, por ejemplo, es el último OID 1.3.6.1.4.1.99990.42.1.1.1.6. Puede compararlo con el número de sesiones que se muestra en el tablero SMP:
6. Agregar nuestros OID a un sistema de supervisión externo
Usaremos Zabbix. La documentación de Zabbix está aquí. Puede encontrar una guía detallada de instalación y configuración de Zabbix en Linux aquí. Se eligió Zabbix como un sistema que no solo le permite trazar gráficas, sino también supervisar texto simple (en nuestro caso, fecha de vencimiento de licencia y unidades de licencia). Después de agregar nuestras 6 métricas a los elementos de nuestro host local (escriba: SNMPv2 agent) y crear 4 gráficas y 2 parámetros de texto simple (como elementos de pantalla), deberíamos ver la siguiente imagen:
Arriba podrá encontrar la información de vencimiento de licencia y número de licencias disponibles. Las gráficas hablan solas.
7. Agregar el inicio del supervisor de sistema a la lista de inicio de nuestro namespace TEST.
Hay un documento bastante bueno sobre las rutinas de usuario ejecutadas cuando Caché se inicia y se detiene. Se llaman %ZSTART y %ZSTOP, respectivamente.
Lo que nos interesa es que el supervisor del sistema (^%SYSMONMGR) se inicie en el namespace TEST durante el inicio del sistema. De forma predeterminada, este supervisor solo se inicia en el namespace %SYS. Por lo tanto, solo miraremos al programa ^%ZSTART. La fuente está en %ZSTART.mac (créelo y guárdelo al namespace %SYS).
%ZSTART; User startup routine.
SYSTEM;
; Cache starting
do $zu(9,"","Starting System Monitor in TEST namespace by ^%ZSTART...Begin")
znspace "TEST"
set sc = ##class(%SYS.Monitor).Start()
do $system.OBJ.DisplayError(sc)
if (sc = 1) {
do $zutil(9,"","Starting System Monitor in TEST namespace by ^%ZSTART...OK")
} else {
do $zutil(9,"","Starting System Monitor in TEST namespace by ^%ZSTART...ERROR")
}
; Starting SNMP
znspace "%SYS"
do start^SNMP
quit
LOGIN;
; a user logs into Cache (user account or telnet)
quit
JOB;
; JOB'd process begins
quit
CALLIN;
; a process enters via CALLIN interface
quit
Otra forma de hacer lo mismo es usar ^%SYSMONMGR:
%SYS>do ^%SYSMONMGR
1. Select item 3, Configure System Monitor Classes.
2. Select item 2, Configure Startup Namespaces.
3. Select item 2, Add Namespace.
Namespace? TEST 4. Select item 1, List Start Namespaces.
Option? 1
TEST 5. Select item 4, Exit.
6. Select item 3, Exit.
7. Select item 8, Exit.
Reiniciamos ahora Caché (de ser posible) para asegurarnos de que las estadísticas SNMP se sigan recolectando después de un reinicio.
Y terminamos. Quizás algunos cuestionen mi elección de parámetros supervisados o código, pero la tarea era mostrar la mera posibilidad de implementar dicha supervisión. Más tarde puede agregar parámetros extra o refactorizar su código.
¡Gracias por vuestra atención!
Muy buen articulo.
Utilizo SNMP personalizado con Zabbix y el artículo cubrió completamente la configuración.
En mi experiencia, una desventaja que encontré es el mantenimiento. Tuve muchos problemas al principio. Recomiendo este protocolo para aquellos que tienen pocos indicadores personalizados para crear y actualizar. Hola Joel,
Buenas tardes, Te saluda desde Argentina Fernando Iglesias
Primero que nada muchas gracias por compartir esta información hace tiempo que estoy tratando de monitorear varios clientes que utilizan HSFOUNDATION2014 con zabbix.
Tengo problemas para crear la clase ¿Me podrás ayudar?
Hola @Fernando.Iglesias de forma independiente a lo que responda Joel, creo que si creas un post con el problema concreto y todos los datos será más fácil que la comunidad pueda tener más información al respecto Hola @Fernando.Iglesias,
Encantado de poder ayudarte! de todos modos, lo que menciona @David.Reche es lo ideal, si publicas tu post acá es mucho más probable que puedas conseguir respuestas más rápido. Te dejo mi mail joel.espinoza@intersystems.com para ir avanzando.
Saludos!
Joel
Artículo
Kurro Lopez · 22 oct, 2019
¡Hola a tod@s!
En este artículo voy a hablar sobre los Globals, esas espadas mágicas para almacenar datos, que han estado con nosotros desde hace tiempo, pero no mucha gente las utiliza de forma eficiente o realmente conoce esta súper herramienta.
Si se utilizan globals para realizar las tareas en donde realmente brillan, los resultados pueden ser sorprendentes, ya sea en términos de un mayor rendimiento o en una simplificación drástica de la solución en general (1, 2).
Globals ofrecen una forma especial de almacenar y procesar datos, la cual es completamente diferente de las tablas SQL. Se introdujeron por primera vez en 1966 con el lenguaje de programación M(UMPS), donde inicialmente se utilizaron en las bases de datos médicas. Todavía se usan de la misma manera, pero también fueron adoptados por otras industrias donde la confiabilidad y el alto rendimiento son la máxima prioridad (como en las finanzas, las operaciones comerciales, etc.)
Posteriormente, el M(UMPS) evolucionó hasta convertirse en Caché ObjectScript (COS), el cual se desarrolló por InterSystems como un superconjunto de M. El lenguaje original aún es aceptado por la Comunidad de Desarrolladores y persiste en algunas implementaciones. Existen varias señales de que todavía tiene actividad en la web: Grupos de Google sobre MUMPS, Grupo de usuarios de Mumps, Normas ISO vigentes, etc.
Los globals que se basan en el moderno Sistema de Gestión de Bases de Datos (DBMS) son compatibles con transacciones, registros, replicaciones y particiones. Esto significa que pueden utilizarse para desarrollar sistemas de distribución que sean modernos, confiables y rápidos.
Los globals no lo restringirán a las limitaciones del modelo relacional. Más bien, le darán libertad para crear estructuras de datos optimizadas con el fin de realizar tareas particulares. Para muchas aplicaciones, el uso razonable de los globals puede ser una verdadera solución inmediata, la cual ofrece velocidades que los desarrolladores de aplicaciones relacionales convencionales solamente pueden soñar.
Los globals funcionan como un método para almacenar datos que puede utilizarse con muchos lenguajes de programación modernos, ya sean de nivel superior o inferior. Por lo tanto, este artículo se centrará específicamente en los globals y no en el lenguaje del que proceden.
2. Cómo funcionan los globals
Primero, entenderemos cómo funcionan los globals y cuáles son las ventajas de utilizarlos. Los globals pueden verse desde diferentes perspectivas. En esta parte del artículo los analizaremos como árboles o como almacenes jerárquicos de datos.
Dicho de forma sencilla, un global es un conjunto persistente. Un conjunto que se guarda automáticamente en el disco.
Es difícil imaginar algo más sencillo para almacenar datos. En el código del programa (escrito con el lenguaje COS/M), la única diferencia que existe respecto a un conjunto asociativo normal es el símbolo ^ que se encuentra antes de sus nombres.
No es necesario tener conocimientos previos de SQL para guardar datos en un global, ya que todos los comandos necesarios son realmente sencillos y pueden aprenderse en una hora.
Comencemos con el ejemplo más sencillo, un árbol de un solo nivel que tiene dos ramas. Los ejemplos están escritos en COS.
Set ^a("+7926X") = "John Sidorov"
Set ^a("+7916Y") = "Sergey Smith"
Cuando los datos se insertan en un global (mediante el comando Set), automáticamente suceden 3 cosas:
Se guardan los datos en el disco.
Se crean índices. Lo que está entre paréntesis es un subíndice, y lo que está a la derecha del signo igual es el valor del nodo "а".
Se ordenan. Los datos se ordenan mediante un elemento. El próximo recorrido colocará a «Sergey Smith» en la primera posición, seguido por «John Sidorov». Cuando se obtiene una lista de usuarios desde un global, la base de datos no dedica tiempo a ordenarla. De hecho, puede solicitar que la lista comience a ordenarse desde cualquier elemento, incluso cuando no exista uno (la salida comenzará a partir del primer elemento verdadero y seguirá después de este).
Todas estas operaciones se realizan a una velocidad increíble. En mi equipo doméstico (que cuenta con las siguientes especificaciones: i5-3340, 16GB, HDD WD 1TB Blue) logré alcanzar las 1,050,000 inserciones/segundo en un solo proceso. En equipos con procesadores multinúcleo, las velocidades pueden alcanzar docenas de milliones de inserciones/segundo.
Desde luego, la velocidad de inserción del registro no proporciona mucha información. Por ejemplo, podemos escribir los datos en archivos de texto, según los rumores, así es como funciona el procesamiento de los datos en Visa. Sin embargo, con los globals, obtenemos un almacenamiento estructurado e indexado que permite trabajar mientras se disfruta de su alta velocidad y facilidad de uso.
La mayor fortaleza de los globals es la velocidad con que es posible insertar nuevos nodos dentro de ellos
Los datos siempre se indexan en un global. Los recorridos en los árboles con un solo nivel y profundidad siempre son muy rápidos
Vamos a añadir algunas ramas de segundo y tercer nivel en el global.
Set ^a("+7926X", "city") = "Moscow"
Set ^a("+7926X", "city", "street") = "Req Square"
Set ^a("+7926X", "age") = 25
Set ^a("+7916Y", "city") = "London"
Set ^a("+7916Y", "city", "street") = "Baker Street"
Set ^a("+7916Y", "age") = 36
Aparentemente, puede construir árboles de varios niveles utilizando globals. El acceso a cualquier nodo es casi instantáneo gracias a la indexación automática que se realiza después de cada inserción. Las ramas de los árboles de cualquier nivel se ordenan mediante un elemento.
Como puede ver, tanto los datos como los valores pueden almacenarse en elementos. La longitud combinada de un elemento (la suma de las longitudes de todos los índices) puede alcanza 511 bytes, y los valores pueden alcanzar hasta 3.6 MB de tamaño en el Caché. El número de niveles de un árbol (número de dimensiones) se limita a 31.
Una última cosa que también es genial: puede construir un árbol sin definir los valores de los nodos de nivel superior.
Set ^b("a", "b", "c", "d") = 1
Set ^b("a", "b", "c", "e") = 2
Set ^b("a", "b", "f", "g") = 3
Los círculos vacíos son nodos que no tienen ningún valor.
Para entender mejor los globals, los compararemos con otros árboles: los árboles en los jardines y los árboles de nombres en el sistema de archivos.
Compararemos los globales con las estructuras jerárquicas más comunes: los árboles que generalmente crecen en los jardines y campos, al igual que los sistemas de archivos.
Como podemos ver, las hojas y los frutos solamente crecen en los extremos de las ramas de los árboles comunes.Sistemas de archivos – La información también se almacena en los extremos de las ramas, también conocida como nombres completos de los archivos.
Aquí puede ver la estructura de los datos en un global.
Las diferencias son:
Nodos internos: en un global la información puede almacenarse en cada nodo, no solo en los extremos de las ramas
Nodos externos: los globals deben tener extremos definidos en sus ramas (extremos con valores), aunque esto no es obligatorio para el sistema de archivos y los árboles en los jardines
En cuanto a los nodos internos, podemos referirnos a la estructura del global como un superconjunto que contiene los árboles con nombres de los sistemas de archivos y las estructuras que conforman los árboles en un jardín. De modo que la estructura del global es más flexible.
En general, un global es un árbol estructurado que es compatible con el almacenamiento de datos en cada nodo.
Para entender mejor cómo funcionan los globals, imaginemos: ¿qué pasaría si los creadores de un sistema de archivos utilizaran un enfoque idéntico al de los globals para almacenar información?
Si el último archivo de una carpeta se eliminara, también se eliminaría la misma carpeta, así como todas las carpetas de nivel superior que solamente contenían la carpeta que se eliminó.
No habría necesidad de utilizar carpetas. Tendríamos archivos con subarchivos y archivos sin subarchivos. Si compara esto con un árbol normal, cada rama se convertiría en una fruta.
Probablemente ya no se necesitarían cosas como README.txt. Todo lo que quisiera decir sobre el contenido de una carpeta podría escribirse en el propio archivo dentro la carpeta. Generalmente, los nombres de los archivos no se distinguen de los nombres de las carpetas (por ejemplo, /etc/readme puede ser cualquiera de los dos, el archivo o la carpeta), lo cual significa que no tendríamos ningún problema si solo manipulamos los archivos.
Las carpetas con subcarpetas y archivos podrían eliminarse mucho más rápido. Existen artículos en la red que relatan historias sobre lo difícil y laborioso que es eliminar millones de archivos pequeños (1, 2, 3). Sin embargo, si crea un sistema de pseudo archivos basado en un global, esto solo llevará segundos o incluso fracciones de segundo. Cuando probé la eliminación de subárboles en el equipo PC que tengo en mi casa, logré eliminar desde 96 hasta 341 millones de nodos de un árbol con dos niveles en un HDD (no en un SDD). Es importante mencionar que nos referimos a la eliminación de una sección en un árbol global, no a la eliminación de un archivo completo que contenga un global.
La eliminación de subárboles es otra de las ventajas de los globals: no necesita la recurrencia para realizar esto. Es increíblemente rápido.
En nuestro árbol, esto puede realizarse mediante el comando Kill.
Kill ^a("+7926X")
A continuación se muestra una pequeña tabla que le permitirá comprender mejor las acciones que puede realizar en un global.
Comandos y funciones clave relacionados con los globales en COS
Set
Configura (inicializa) las ramificaciones hasta uno de los nodos (si no se definieron) y el valor del nodo
Merge
Realiza la copia de un subárbol
Kill
Elimina un subárbol
ZKill
Elimina el valor de un nodo específico. No afecta al subárbol que se deriva del nodo
$Query
Muestra el recorrido y la profundidad completa del árbol
$Order
Devuelve el siguiente subíndice que está en el mismo nivel
$Data
Comprueba si un nodo está definido
$Increment
Muestra el incremento atómico del valor de un nodo para evitar que ACAD (ACID, por sus siglas en inglés) lo lea y escriba. Por último, se recomienda utilizar $Sequence en su lugar.
Gracias por leer mi artículo, estaré encantado de responder vuestras preguntas.
Descargo de responsabilidad: Este artículo refleja la opinión personal del autor y no tiene ninguna relación con la opinión oficial de InterSystems.
Artículo
Luis Angel Pérez Ramos · 16 jun, 2025
¡Hola, estimados miembros de nuestra comunidad de desarrolladores!
En el artículo de hoy vamos a echar un vistazo a una de las últimas funcionalidades de telemonitorización de nuestras instancias de IRIS que se han añadido al producto. Estoy hablando de la compatibilidad con OpenTelemetry.
¿Qué es OpenTelemetry?
OpenTelemetry es un framework open source que proporciona las herramientas necesarias como SDKs y estándares para implementar la observabilidad en sistemas y aplicaciones.
Esta observabilidad se extiende a tres tipos de datos:
Trazas: control del flujo de la información que fluye a lo largo de las soluciones mediante la inclusión de trazas que permitan identificar por donde están pasando y en qué condiciones.
Métricas: situación del sistema, rendimiento del mismo, latencias en las respuestas, uso de recursos, etc.
Logs: incluidos en los sistemas para una mejor comprensión de los eventos que ocurren.
OpenTelemetry hace uso del estándar abierto OTLP que define como se debe serializar y transportar toda la telemetría anteriormente definida, el envío de esta telemetría puede realizarse mediante HTTP o gRPC.
Open Telemetry con IRIS
InterSystems IRIS aprovecha las funcionalidades disponibles por OpenTelemetry SDK para permitir la exportación de toda la telemetría que genera la instancia configurada. ¿De donde procede dicha telemetría?
Métricas: proceden de la información que tenemos disponible desde la API REST /api/monitor (aquí podéis ver la documentación oficial de dicha API).
Logs: mensajes que se registran en el archivo messages.log e información que se almacena en la base de datos de auditoría (si está activada).
Trazas: trazas definidas por el usuario dentro de las aplicaciones desarrolladas en la instancia.
Pues bien veamos como procederíamos a configurar OpenTelemetry en una instancia de IRIS
Configurando IRIS con OpenTelemetry
Para nuestro ejemplo de configuración he utilizado un proyecto que tengo subido en un github que corre en Docker, pero no seria muy complicado configurarlo en una instancia instalada on-premise. Tenéis dicho proyecto subido en OpenExchange asociado a este artículo.
Antes de mostrar las distintas configuraciones vamos a explicar que elementos van a formar parte de nuestro "ecosistema de monitorización":
InterSystems IRIS
La instancia de IRIS será la encargada de generar los datos de telemetría que necesitamos monitorizar.
OpenTelemetry Collector
Es una herramienta proporcionada por OpenTelemetry encargada de recopilar datos telemétricos de diferentes orígenes, en nuestro ejemplo sólo será IRIS, pero podríamos añadir tantos como necesitáramos.
Prometheus
Herramienta open-source utilizada para la monitorización de sistemas y generación de alertas. Esta herramienta será la responsable de la recepción de las métricas acumuladas por OpenTelemetry Collector.
Jaeger
Plataforma open-source para la gestión y monitorización de trazas de sistemas basados en microservicios.
Configuración en Docker
Como he comentado anteriormente, he usado un despliegue en Docker para simplificar el ejemplo lo máximo posible. Analicemos el fichero docker-compose.yml por partes para su mejor comprensión.
Imagen de IRIS
iris:
init: true
container_name: iris
build:
context: .
dockerfile: iris/Dockerfile
ports:
- 52774:52773
- 51774:1972
volumes:
- ./iris/shared:/iris-shared
environment:
- ISC_DATA_DIRECTORY=/iris-shared/durable
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
command: --check-caps false --ISCAgent false
Como véis estoy definiendo la imagen a usar en un fichero Dockerfile que no tiene mayor interés y por lo tanto no me molestaré en explicarlo. El punto interesante de esta configuración será la definición de una variable de entorno llamada OTEL_EXPORTER_OTLP_ENDPOINT que será la URL donde nuestro OpenTelemetry Collector estará esperando que IRIS le envíe todas las métricas, trazas y logs de los que dispone.
Una vez que despleguemos nuestra instancia de IRIS tendremos que configurarla para que emita las métricas y los logs de forma regular, para ello accederemos desde el portal de gestión a la configuración de Monitor:
Como podéis ver podemos habilitar tanto el envío de Métricas como el de Logs, en el caso de las trazas estas no se envían en intervalos, sino que se enviarán en cuanto se invoque el método de "End" de la instancia de la clase %Trace.Provider, no entraré más en detalle, pero podéis revisar aquí la documentación oficial.
Imagen de OpenTelemetry Collector
otel-collector:
build:
context: .
dockerfile: open-telemetry/Dockerfile
container_name: otel-collector
command: ["--config=/otel-local-config.yml"]
volumes:
- ./open-telemetry/otel-collector-config.yml:/otel-local-config.yml
ports:
- 4317:4317 # OTLP gRPC receiver
- 4318:4318 # OTLP HTTP receiver
- 9464:9464 # Metrics
depends_on:
- iris
Aquí tenemos la imagen de OpenTelemetry Collector, nuevamente he definido un Dockerfile para definir de donde obtener la imagen (no sería necesario). Como véis estamos haciendo accesibles 3 puertos:
4317: puerto para la recepción de métricas, trazas y logs vía gRPC.
4318: puerto para la recepción de métricas, trazas y logs vía HTTP.
9464: puerto abierto para que terceras herramientas consulten la información recibida por OpenTelemetry Collector.
También hemos declarado un archivo de configuración otel-local-config.yml (el nombre es modificable). Echemos un vistazo a su interior:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
exporters:
otlp:
endpoint: jaeger:4317
tls:
insecure: true
prometheus:
endpoint: "0.0.0.0:9464"
debug: {}
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp]
metrics:
receivers: [otlp]
exporters: [prometheus]
logs:
receivers: [otlp]
exporters: [debug]
¿Qué estamos viendo aquí? Muy sencillo, tenemos las siguiente secciones:
Receptores de datos, en nuestro caso lo hemos llamado otlp, en el que configuramos la IP y los puertos en los que nuestro receptor va a esperar la información de terceros sistemas.
Exportadores de datos, a donde vamos a enviar o quién va a extraer los datos que sean recibidos en el OpenTelemetry Collector. Para nuestro ejemplo hemos usado un exporter ya incluido en OpenTelemetry como es prometheus que se encargará de obtener las métricas en la instancia local del OpenTelemetry Collector en el puerto 9464. Para el caso de jaeger estamos usando directamente la capacidad de OpenTelemetry de remitir datos directamente a una IP (jaeger es el nombre de la instancia dentro de la red montada por Docker) que corresponderá con nuestra instancia de jaeger.
Servicios, donde indicaremos quienes de los componentes que hemos configurado como receptores y exportadores se van a hacer cargo de la recepción y emisión de métricas, trazas y logs. Cómo podéis ver en nuestro caso, el OpenTelemetry Collector será el receptor que usaremos para todo, Prometheus será el receptor de las métricas y Jaeger, mediante OpenTelemetry Collector, será el receptor de las trazas.
Imagen de Prometheus
Echemos un vistazo a su configuración en Docker:
prometheus:
build:
context: .
dockerfile: prometheus/Dockerfile
container_name: prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- 9090:9090
Cómo podéis observar es muy sencillo, tenemos nuestra imagen definida nuevamente en un archivo Dockerfile que únicamente hace referencia al nombre de la imagen, un puerto 9090 donde se desplegará la interfaz web a la que podremos acceder y finalmente un archivo de configuración llamado prometheus.yml
Veamos que nos cuenta ese archivo prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'otel-collector'
static_configs:
- targets: ['otel-collector:9464']
Tenemos la siguiente configuración:
Scrape interval: con el intervalo de tiempo entre consulta y consulta al OpenTelemetry Collector.
Scrape configs: donde damos un nombre a la tarea que realizará la búsqueda y la IP y el puerto al que se conectará para dicha búsqueda.
Imagen de Jaeger
Para la imagen de Jaeger he cogido directamente un ejemplo disponible en nuestro querido chatGPT:
jaeger:
image: jaegertracing/all-in-one:latest
container_name: jaeger
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- 16686:16686 # Jaeger UI
- 14250:14250 # OTLP gRPC receiver
El punto más relevante, a parte del puerto 16689 que es el que usaremos para acceder a la interfaz web, es la variable de entorno COLLECTOR_OTLP_ENABLED, la cual nos habilitará los puertos por defecto de Jaeger para permitir las conexiones HTTP desde OpenTelemetry Collector. Aquí podéis ver la lista de puertos que usa, pero ya os adelanto que es 4317 para la transmisión gRCP y el 4318 para la HTTP, como habéis visto en la configuración de OpenTelemetry Collector, vamos a hacer uso de la conexión vía gRCP.
Pues bien, con todo preparado, veámoslo en funcionamiento arrancando el proyecto.
Emitiendo métricas, recepción en Prometheus
Ya tenemos nuestras instancias configuradas y arrancadas, accedamos a la la interfaz web que nos proporciona Prometheus, en nuestro ejemplo lo hemos mapeado a http://localhost:9090 , por defecto se nos mostrará donde podemos lanzar consultas sobre las métricas disponibles.
Si hemos configurado correctamente la conexión entre IRIS - OpenTelemetry Collector - Prometheus, desde la pantalla de consultas de Prometheus tendremos acceso a todas las métricas estándar disponibles de IRIS como podéis ver en el siguiente pantallazo buscando por "iris_"
Si seleccionamos cualquiera de ellas podremos ver un gráfico a lo largo del tiempo:
Enviando trazas a Jaeger
Para comprobar el funcionamiento del envío de trazas vamos a usar el recurso más sencillo que nos pone a disposición IRIS, que es el método TestTraces() de la clase SYS.Monitor.OTel que podéis consultar aquí, si tenéis algún interés particular en verlo más en detalle comentádmelo en los comentarios y estaré encantado de escribir un artículo al respecto.
Nos limitamos a ejecutar por consola el comando desde el namespace SYS:
%SYS>do ##class(SYS.Monitor.OTel).TestTraces()
Esto nos remitirá una traza que deberá haber llegado a Jaeger, comprobémoslo desde su interfaz gráfica desplegada en http://localhost:16686
En los filtros del menú de la izquierda podemos ver que tenemos un servicio llamado irisotel, este servicio es el usado por IRIS para probar el envío de trazas de prueba, de ahí el nombre de la traza recibida test_trace.
¡Pues ya estaría! Ya tenemos nuestra instancia preparada para el envío de todos los datos de métricas y trazas que deseamos. Estoy a vuestra disposición si queréis profundizar más en el tema.
Artículo
Yuri Marx · 15 ene, 2021
¡Hola Comunidad!
En este artículo, comparo las características de los principales líderes en Operational Database Management Systems (ODBMS) del cuadrante mágico de Gartner (2019). La lista está clasificada por número de características existentes.
InterSystems IRIS 2020.3 - 58 características (https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls)
Oracle Database 21c - 54 características (https://docs.oracle.com/en/database/oracle/oracle-database/index.html)
Microsoft SQL Server - 45 características (https://docs.microsoft.com/en-us/sql/sql-server/?view=sql-server-ver15)
AWS Aurora - PostgreSQL - 34 características (https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_AuroraOverview.html)
Solo comparo características, no rendimiento (ver el informe ESG para esa comparación). Para realizar este artículo, utilicé los enlaces a la documentación oficial de cada producto, mostrados más arriba.
Esta es la tabla con la comparación:
Características
InterSystemsIRIS 2020.3
OracleDatabase 21c
MicrosoftSQL Server 2020
AWS Aurora -PostgreSQL
Failover Cluster
Sí
Sí
Sí
Sí
Mirroring/Data Replication
Sí
Sí
Sí
Sí
Distributed Cache/In Memory support
Sí
Sí
Sí
Sí
Backup/Restore - Incremental and Full
Sí
Sí
Sí
Sí
Vertical Scaling
Sí
Sí
Sí
Sí
Horizontal Scaling for Insert, Update and Delete
Sí
Sí
No
No
Horizontal Scaling for Select
Sí
Sí
Sí
Sí
Sharded Cluster
Sí
Sí
No
Sí
Cloud Support and Cloud Manager
Sí
Sí
Sí
Sí
Kubernetes Support and Kubernetes Manager
Sí
Sí
Sí
Sí
Docker support
Sí
Sí
Sí
No
AWS Hosting
Sí
Sí
Sí
Sí
Azure Hosting
Sí
Sí
Sí
No
Google Cloud Hosting
Sí
Sí
Sí
No
Managed Cloud
Sí
Sí
Sí
Sí
On-premises support
Sí
Sí
Sí
No
Multimodel - OO
Sí
No
No
No
Multimodel - Document - JSON
Sí
Sí
Sí
Sí
Multimodel - XML
Sí
Sí
Sí
Sí
Multimodel - Key/Value
Sí
No
No
No
Multimodel - SQL
Sí
Sí
Sí
Sí
Multimodel - Spatial
No
Sí
Sí
Sí
Multimodel - Graph
No
Sí
Sí
No
Multimodel - OLAP Cubes
Sí
Sí
Sí
No
GIS platform
No
Sí
No
No
Native OO programming language
Sí
Sí
No
No
Java, .Net, Python, C/C++ and PHP support
Sí
Sí
Sí
Sí
Node.js support
Sí
Sí
Sí
Sí
ODBC/JDBC support
Sí
Sí
Sí
Sí
Backend Application Development
Sí
Sí
No
No
Frontend Application Development
Sí
Sí
No
No
Low Code Web Application Development
No
Sí
Sí
No
Database Application Development
Sí
Sí
Sí
Sí
OData support
No
Sí
Sí
No
REST Services
Sí
Sí
Sí
Sí
SOAP Services
Sí
No
No
No
Terminal tools
Sí
Sí
Sí
Sí
IDE Support
Sí
Sí
Sí
Sí
Web Admin/IDE Support
Sí
Sí
Sí
Sí
Embedded NLP
Sí
No
No
No
Embedded AutoML
Sí
No
No
No
R/Machine Learning support
Sí
Sí
Sí
Sí
PMML
Sí
No
No
No
Business Report Server/Development
Sí
Sí
Sí
No
Autonomous AI operation
No
Sí
No
No
Unstructured Text Annotations/Apache UIMA Like
Sí
Sí
No
No
Spark support
Sí
Sí
Sí
Sí
BI tool
Sí
No
Sí
No
MDX Support
Sí
Sí
Sí
No
Interoperability Connectors
Sí
No
No
No
BPEL/Integration Orchestration/Workflows
Sí
No
No
No
ETL - Extract, Transform and Load data
Sí
No
No
No
IoT/MQTT support
Sí
No
No
No
EDI support
Sí
No
No
No
ESB
Sí
No
No
No
CDC - Change Data Capture
Sí
Sí
Sí
Sí
RBAC model
Sí
Sí
Sí
Sí
LDAP support
Sí
Sí
Sí
Sí
Authorization/Authentication with two factor support
Sí
Sí
Sí
Sí
Cryptography
Sí
Sí
Sí
Sí
Labeling
Sí
Sí
Sí
No
Audit and trace
Sí
Sí
Sí
Sí
SAM
Sí
Sí
Sí
Sí
Multi OS support
Sí
Sí
Sí
Sí
SAML/Oauth/OpenID support
Sí
Sí
Sí
Sí
Performance Tunning IDE/Pack
No
Sí
No
No
Previleged User Access Management
No
Sí
No
No
Total de Características
58
54
45
34
Artículo
Muhammad Waseem · 28 jul, 2022
![ObjectScript Kernel Logo][ObjectScript Kernel Logo]
[Jupyter Notebook](https://jupyter.org/) es un entorno interactivo formado por celdas que permiten ejecutar código en un gran número de lenguajes de marcado y programación diferentes.
Para hacer esto, Jupyter debe conectarse a un kernel apropiado. No había un Kernel ObjectScript, por lo que decidí crear uno.
Puedes probarlo [aquí](objectscriptkernel.eastus.cloudapp.azure.com).
Este es un adelanto de los resultados:

## Jupyter Kernels 101
Hay varias formas de crear un [Jupyter Kernel](https://jupyter-client.readthedocs.io/en/stable/kernels.html). Decidí hacer un kernel contenedor de Python.
Tenemos que crear una subclase de ```ipykernel.kernelbase.Kernel``` e implementar el método ```do_execute``` que recibe un código para ser ejecutado en un idioma en particular.
Entonces, la idea general es obtener un fragmento de código ObjectScript, ejecutarlo de alguna manera y devolver los resultados a nuestro cuaderno.
Pero, ¿cómo hacemos eso exactamente? Vamos a tratar de analizarlo un poco más.
## Envío de código ObjectScript a IRIS
Para empezar, tenemos que enviar nuestro fragmento de código a IRIS. Aquí es donde [la API Nativa de IRIS para Python](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=PAGE_PYTHON_NATIVE) es necesaria.
Todo lo que tenemos que hacer es importar el paquete ```irisnative```, luego establecer una conexión:
```
def get_iris_object():
# Crear conexión con InterSystems IRIS
connection = irisnative.createConnection('iris', 51773, 'IRISAPP', '_SYSTEM', 'SYS')
# Crear un objeto de iris
return irisnative.createIris(connection)
```
Después de eso, podemos usar la conexión para llamar a las clases que están almacenadas en la base de datos de IRIS.
```
def execute_code(self, code):
class_name = "JupyterKernel.CodeExecutor"
return self.iris.classMethodValue(class_name, "CodeResult", code)
```
¿Para qué se utilizan esta clase ```Codes Executor``` y el método ```CodeResult```?
Vamos a verlo.
## Ejecutar código ObjectScript
El propósito de esta clase es ejecutar una línea de código ObjectScript y devolver un objeto JSON con los resultados de la ejecución. Pasamos nuestro código a ```CodeResult``` en una variable ```vstrCommand```.
Comenzamos redirigiendo IO a la rutina actual, luego ejecutamos el código pasado a través del comando [XECUTE](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_cxecute), redirigimos IO de vuelta al original y devolvemos los resultados.
```
Include %sySystem
Class JupyterKernel.CodeExecutor
{
ClassMethod CodeResult(vstrCommand As %String) As %String [ ProcedureBlock = 0 ]
{
set tOldIORedirected = ##class(%Device).ReDirectIO()
set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
set tOldIO = $io
try {
set str=""
set status = 1
//Redirigir IO a la rutina actual: utiliza las etiquetas definidas a continuación
use $io::("^"_$ZNAME)
//Habilitar redirección
do ##class(%Device).ReDirectIO(1)
XECUTE (vstrCommand)
} catch ex {
set str = ex.DisplayString()
set status = 0
}
//Vuelva a la configuración original de redirección/rutina mnemotécnica
if (tOldMnemonic '= "") {
use tOldIO::("^"_tOldMnemonic)
} else {
use tOldIO
}
do ##class(%Device).ReDirectIO(tOldIORedirected)
quit {"status":(status), "out":(str)}.%ToJSON()
rchr(c)
quit
rstr(sz,to)
quit
wchr(s)
do output($char(s))
quit
wff()
do output($char(12))
quit
wnl()
do output($char(13,10))
quit
wstr(s)
do output(s)
quit
wtab(s)
do output($char(9))
quit
output(s)
set str = str _ s
quit
}
}
```
## Mostrar los resultados
Hemos ejecutado una parte del código ObjectScript, ¿y ahora qué? Bueno, tenemos que mostrar los resultados.
Si no hubo excepciones, simplemente mostramos los resultados línea por línea.
Sin embargo, si nuestro fragmento de código aprobado generó una excepción, detenemos la ejecución, mostramos el número de la línea fallida, a sí misma y la excepción generada.

## Lanzamiento de la aplicación
Puedes probar este kernel por ti mismo y aquí te mostramos cómo.
## Requisitos previos
Asegúrate de tener [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) y [Docker](https://www.docker.com/products/docker-desktop) instalado.
Clonar/extraer el repositorio en cualquier directorio local, por ejemplo, como se muestra a continuación:
```
$ git clone https://github.com/Vekkby/objectsriptkernel.git
```
Abre el terminal en este directorio y ejecuta:
```
$ docker-compose up -d --build
```
## Cómo trabajar con él
Puedes acceder al servidor del notebook desde el navegador usando
```
localhost:8888
```
Hay un notebook de muestra llamado 'hello.ipynb' en el directorio 'work'.

[ObjectScript Kernel Logo]: https://i.imgur.com/L4pi1da.jpg
Artículo
Carlos Castro · 1 jun, 2025
Buenas a todos,
en capítulos anteriores, vimos como "Proteger los datos: Se un mejor cerrajero", en el que explicábamos como proteger nuestros recursos aplicando un control extra al acceso mediante OAUTH2 que nos ofrece Intersystems. Y como no hay 2 sin 3, aquí tenemos un tercer articulo, en el cual vamos a explicar como "avisar a la policía" de que alguien malicioso está intentando acceder a nuestros datos.
Al finalizar el mismo, comentábamos que si quisiéramos podríamos tener un mayor control de estos accesos incorrectos y esto nos lleva a este artículo, por lo que seguiremos el siguiente índice, en el cual podréis encontrar todos los apartados en formato tutorial, de forma que siguiendo paso a paso lograreis llegar al objetivo:
1.- Introducción
1.1.- De donde venimos
2.- Configuración del "091"
2.1.- Arquitectura de las alertas
2.2.- ¿Quién es quién?
2.2.1.- Operación 2.2.2.- Proceso
2.3.- ¿Qué es un bot de telegram?
2.3.1.- Bot 2.3.2.- Grupos 2.3.3.- Configuración de la alerta
3.- Actualización de consulta de usuario
3.1.- Usuario de nuestro token por Endpoints Relacionados con Autenticación (OAUTH)
3.2.- Endpoints útiles para OAUTH
3.3.1.- token 3.3.2.- introspection 3.3.3.- registration 3.3.4.- revocation
1.- Introducción
1.1.- De donde venimos
En el articulo anterior (Proteger los datos: Se un mejor cerrajero) explicábamos como conocer el usuario ("2.1.- ¿Cómo funciona la llave (Token)?") mediante el uso de JWT extrayendo el parámetro "aud" y en base a este valor validábamos por LookUp Table su permiso de acceso a unos recursos o no. Si un usuario no esta autorizado a acceder a determinados recursos, necesitamos saber de estos intentos maliciosos de acceso para poder actuar.
En los siguientes pasos explicaremos la configuración de las alertas por mensajería push así como mejorar la obtención del usuario y mas funciones de las URLS de OAUTH2.
Estas alertas por mensajería push las realizaremos por medio de Telegram, aplicación de mensajería instantánea que nos permite recibir mensajes de texto en tiempo real. Este es el gran punto a su favor para esta situación, pues en el mismo momento que ocurra un intento indebido de acceso a nuestros recursos, lo sabremos y podremos actuar lo antes posible para proteger nuestros datos. Al mismo tiempo, al utilizar una aplicación de este tipo, podremos recibir la notificación en cualquier lugar del mundo, pues, mientras tengamos conexión a internet, esta notificación nos va a llegar.
Con todo esto, ¡VAMOS AL LIO!
2.- Configuración del "091"
2.1.- Arquitectura de las alertas
Partimos de un esquema como el mostrado, donde queremos generar un aviso a nuestro dispositivo móvil en el momento en el que detectemos que hay algún problema, en este caso, un acceso indebido a nuestros recursos.
Por lo tanto, teniendo ya montada nuestra integración, lo que vamos a hacer ahora es añadir un modulo de alertas para Telegram. Este modulo consistirá en 2 componentes principales:
Nombre del Componente
Descripción del Componente
Operaciones.Notificaciones.Telegram
Operación preconfigurada para realizar notificaciones urgentes vía Telegram desde el ESB.
Proceso.Notificaciones.Telegram
Proceso que orquestará los diferentes tipos de notificaciones REST vía Telegram.
2.2.- ¿Quién es quién?
2.2.1.- OperaciónNuestra operación de envío de alertas a Telegram realizará notificaciones REST vía Telegram. Esta operación “simulara” ser un bot en la aplicación de Telegram y desde el ESB se controlará este bot, que emitirá las notificaciones a los grupos y/o personas según los acuerdos alcanzados para cada caso. Estas notificaciones serán inmediatas pudiendo aplicar un control temporal para no saturar los dispositivos móviles de las personas notificadas.
Estas notificaciones podrán sustituir y/o complementar a las alertas ya vigentes (correo electrónico). Ya que su objetivo es que las personas responsables de cada aplicativo sean informadas lo antes posible de problemas ocurridos.
2.2.2.- ProcesoNuestro proceso orquestará la configuración y parametrización de las notificaciones REST vía Telegram. Las diferentes casuísticas que consideremos realizarán llamadas a este proceso enviando la información solicitada para cada caso y este proceso preparará los mensajes personalizados para cada caso.
Llegados a este punto, sabemos que vamos a utilizar una operación rest para notificar vía Telegram, pero nos queda explicar lo mas importante, ¿Qué es un bot de telegram? A continuación lo explicamos.
2.3.- ¿Qué es un bot de telegram?
Para la notificación instantánea de mensajería que se pretende implantar haremos uso de una de las principales funcionalidades de código abierto que nos da la API de Telegram como es la creación y administración de bots.
2.3.1.- Bot
Un bot es una funcionalidad de Telegram, la cual consiste en la creación de un perfil de usuario de Telegram que se controla por terceros gracias a unos parámetros de configuración definidos por la API. Esta funcionalidad simula ser un usuario de Telegram, y como todo usuario, puede enviar mensajería a través de la aplicación por medio de configuraciones del bot. En el ESB configuraremos este bot para que de forma automatizada envíe notificaciones a los diversos grupos que se hayan acordado.
Para la creación de un bot que utilizaremos para enviar notificaciones vía Telegram, será necesario hacer uso de la funcionalidad ofrecida por el @botfather de Telegram.
El @botfather es una funcionalidad de Telegram para controlar a otros bots y crearlos por cuenta propia y es una de las recomendaciones de los responsables de Telegram para crear de una forma más fácil tu propio bot. Esta funcionalidad dispone de una variedad amplia de comandos para configurar nuestro bot.
Para empezar a crear nuestro bot, deberemos de seguir los siguientes pasos (7):
Spoiler
1.- Abrir una conversación con @botfather
2.- Pulsar en Start/Empezar para empezar a chatear con el
3.- Escribiendo “/help” podremos visualizar los diversos comandos, y empezaremos por el comando “/newbot”
4.- Una vez introducimos el comando “/newbot” nos mostrará lo siguiente:
5.- Deberemos elegir un nombre para nuestro bot:
6.- A continuación le asignaremos un username a nuestro bot:
7.- Una vez asignado el username nos contestará que hemos conseguido crear nuestro bot, indicándonos Token único el cual deberemos guardar de forma segura y protegida ya que este Token es el que nos permitirá manejar nuestro bot de forma automatizada y sólo debe estar disponible para las personas autorizadas para el manejo del bot.
2.3.2.- GruposPara realizar las notificaciones se hará mediante grupos privados de Telegram, en los cuales se incluirá en cada grupo a las personas acordadas.
Para crear un grupo deberemos abrir la aplicación de Telegram y seguir los siguientes pasos:
Spoiler
1.- Opciones > Nuevo Grupo
2.- Introducir el nombre del Grupo siguiendo la nomenclatura acordada
3.- En la Gestión del Canal, se deberá configurar el canal como privado para que solo puedan entrar en el canal las personas invitadas.
4.- El siguiente paso será añadir a las personas que deben estar en cada grupo.
5.- Una vez añadidos los invitados, ya podrán visualizar en el grupo las notificaciones que el bot envíe de forma automatizada.
2.3.2.- Configuración de la alerta
Ahora que ya tenemos nuestro bot y nuestro grupo de Telegram donde van a llegar las notificaciones, debemos orquestar en el ESB como enviarlas.
Os dejo por aquí un esqueleto de operación de envío de peticiones:
Spoiler
Class Operaciones.REST.Notificaciones.Telegram Extends EnsLib.REST.Operation
{
Parameter INVOCATION = "Queue";
Method notificar(pRequest As Mensajes.Request.Notificaciones.TelegramRequest, Output pResponse As Mensajes.Response.Notificaciones.TelegramResponse) As %Status
{
set pResponse = ##class(Mensajes.Response.Notificaciones.TelegramResponse).%New()
try {
// Preparamos la url para la petición
set url = ##class(Util.TablasMaestras).getValorMaestra("BOT_TELEGRAM","url")
// A través de una LookUp Table, guardamos el TOKEN del bot de telegram que va a enviar el mensaje
set bot = ##class(Util.TablasMaestras).getValorMaestra("BOT_TELEGRAM","bot")
// A través de una LookUp Table, guardamos el metodo de la API Telegram para envío de notificaciones
set method = ##class(Util.TablasMaestras).getValorMaestra("BOT_TELEGRAM","method")
// A través de una LookUp Table, guardamos el ID del chat de telegram donde vamos a enviar el mensaje
set chatId = ##class(Util.TablasMaestras).getValorMaestra("BOT_TELEGRAM",pRequest.aplicacion)
set URL = url_bot_method_chatId_"&text="_pRequest.mensaje
// LA URL resultante de consumo de la API Telegram sería con la siguiente estructura:
// https://api.telegram.org/bot1234567890:[TOKEN]/sendMessage?chat_id=[CHATID]&text="_pRequest.mensaje
set tSC=..Adapter.PostURL(URL,.tHttpResponse)
Set textoError = $System.Status.GetErrorText(tSC)
If $$$ISERR(tSC)
{
While (tHttpResponse.Data.AtEnd = 0) {
set linea = tHttpResponse.Data.Read()
}
} else {
While (tHttpResponse.Data.AtEnd = 0) {
set linea = tHttpResponse.Data.Read()
}
}
} catch {
// se captura el error y se imprime
Set tSC=$$$SystemError
// se carga el resultado en el response
Set tSC=$$$SystemError
set textoError = $System.Status.GetErrorText(tSC)
set pResponse.return = "010 - "_textoError
}
Quit $$$OK
}
XData MessageMap
{
<MapItems>
<MapItem MessageType="Mensajes.Request.Notificaciones.TelegramRequest">
<Method>notificar</Method>
</MapItem>
</MapItems>
}
}
En la misma podéis observar que se necesita atacar a la URL con unos parámetros determinados:
https://api.telegram.org/bot1234567890:[TOKEN]/sendMessage?chat_id=[CHATID]&text="_pRequest.mensaje
En este punto, podemos ver que hay un valor que no disponemos, que es el CHAT ID. Este valor es muy fácil de obtener, pues con reenviar un mensaje desde nuestro nuevo grupo al @userinfobot de Telegram, este nos devolverá el ID del grupo como muestro a continuación:
La producción nos debería quedar algo así:
Una vez enviamos la petición REST desde el ESB, podemos ver la notificación en el Telegram.
Esta operación nos vale para todo caso que queramos notificar, pues el Proceso anteriormente indicado, será el encargado de orquestar a que grupo llamar (CHAT ID) y que mensaje enviar (adaptado a cada caso), De forma que podamos tener N grupos y N mensajes diferenciados utilizando el mismo Bot.
A continuación os dejo un breve video explicativo del proceso completo:
Como podéis observar, siguiendo los pasos de esta guía, podréis notificar a quien queráis de cualquier cuestión que ocurra en el ESB de forma inmediata. ¡Así que espero que os sea de ayuda!
No puedo terminar este articulo sin incluir una mejora del articulo anterior, y esta es la utilización de la consulta del usuario así como el uso de algunas de las URLs que nos ofrece el servidor OAUTH2 de Intersystems. Todo esto lo explicamos a continuación.
3.- Actualización de consulta de usuario
3.1.- Usuario de nuestro token por Endpoints Relacionados con Autenticación (OAUTH)
A continuación os explico una forma mas sencilla y reutilizable de obtener este dato de forma que podamos aplicarlo para múltiples procesos.
En el primer articulo ("Como controlar el acceso a tus recursos con OAuth2") en el que explicábamos como preparar el servidor OAUTH2 de Intersystems, indicábamos que la URL del servidor era:
https://DNS/IP:57773/oauth2
Una vez creamos el servidor, en su configuración podemos ver:
Sistema > Gestión de seguridad > Cliente de OAuth 2.0 > Descripción del servidor - (Configuración de seguridad)
Spoiler
Como vemos, hay varias URLs que nos crea el servidor. Una de ellas es para consultar la información del usuario (userinfo) en OAuth 2.0, podemos utilizar la siguiente URL:
https://DNS/IP:57773/oauth2/userinfo
El procedimiento a seguir es enviar una solicitud GET al punto final UserInfo, incluyendo el token de acceso en el encabezado de la solicitud de autorización. El encabezado suele ser "Authorization: Bearer [token_de_acceso]". Para ello os muestro el paso a paso:
Obtener el Token para el usuario "bezkfeZoA3mU2g1dmABlvgv9k1AKHN78JtIgXgcdQeQ":
Spoiler
Utilizando este Token hacia la URL de UserInfo obtenemos el usuario:
Spoiler
Como podéis ver, con un simple GET a esa URL y el token que ya disponemos, nos devuelve "sub" que es el "aud" que obteníamos por el JWT. De forma que podemos montarnos una operación REST en el ESB que realice esta operación y tendremos la respuesta inmediata.
Os dejo por aquí una estructura de una operación REST para que podáis estructurar vuestras pruebas:
Spoiler
Class Operaciones.REST.OAUTH2.Gestion Extends EnsLib.REST.Operation
{
Method consultaUsuario(pRequest As Mensajes.Request.OAUTH2.consultaUsuario, Output pResponse As Mensajes.Response.OAUTH2.consultaUsuario) As %Status
{
set pResponse = ##class(Mensajes.Response.OAUTH2.consultaUsuario).%New()
try {
//preparamos la url para la petición
set url = "IP"
set port = "PUERTO"
set method = "/oauth2"
set method2 = "/userinfo"
//quedando la URL = https://IP:PUERTO/oauth2/userinfo
set URL = "https://"_url_":"_port_method_method2
set tSC = ..Adapter.GetURL(URL,.res)
If $$$ISERR(tSC)
{
// Tratamiento del error si falla la llamada
} else {
// EXTRAEMOS EL JSON
set linea = ""
While (res.Data.AtEnd = 0) {
set linea = linea_res.Data.Read()
}
// se transforma el objeto JSON a un objeto local
set claseAux = ##class(%ZEN.Auxiliary.jsonProvider).%New()
set tSC= claseAux.%ConvertJSONToObject(.linea,"Mensajes.Response.OAUTH2.consultaUsuario",.objeto,1)
// TRATAMIENTO QUE QUERAMOS HACER DE LOS DATOS
}
} catch {
// Tratamiento del error
}
Quit $$$OK
}
XData MessageMap
{
<MapItems>
<MapItem MessageType="Mensajes.Request.OAUTH2.consultaUsuario">
<Method>consultaUsuario</Method>
</MapItem>
</MapItems>
}
}
3.2.- Endpoints útiles para OAUTH
La URL del UserInfo la podemos obtener llamando a la URL:
https://DNS/IP:PUERTO/oauth2/.well-known/openid-configuration
La cual nos entrega el siguiente JSON que os sonará:
Spoiler
{
"issuer" : "https://DNS/IP:PUERTO/oauth2",
"authorization_endpoint" : "https://DNS/IP:PUERTO:57776/oauth2/authorize",
"token_endpoint" : "https://DNS/IP:PUERTO:57776/oauth2/token",
"userinfo_endpoint" : "https://DNS/IP:PUERTO/oauth2/userinfo",
"revocation_endpoint" : "https://DNS/IP:PUERTO/oauth2/revocation",
"introspection_endpoint" : "https://DNS/IP:PUERTO/oauth2/introspection",
"jwks_uri" : "https://DNS/IP:PUERTO/oauth2/jwks",
"registration_endpoint" : "https://DNS/IP:PUERTO/oauth2/register",
"scopes_supported" : [ "openid", "profile", "email", "address", "phone", "my/scope" ],
"response_types_supported" : [ "code" ],
"response_modes_supported" : [ "query", "fragment", "form_post" ],
"grant_types_supported" : [ "authorization_code", "client_credentials", "refresh_token" ],
"id_token_signing_alg_values_supported" : [ "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" ],
"id_token_encryption_alg_values_supported" : [ "none", "RSA1_5", "RSA-OAEP", "A128KW", "A192KW", "A256KW", "dir" ],
"id_token_encryption_enc_values_supported" : [ "none", "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512" ],
"userinfo_signing_alg_values_supported" : [ "none", "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" ],
"userinfo_encryption_alg_values_supported" : [ "none", "RSA1_5", "RSA-OAEP", "A128KW", "A192KW", "A256KW", "dir" ],
"userinfo_encryption_enc_values_supported" : [ "none", "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512" ],
"access_token_signing_alg_values_supported" : [ "none", "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" ],
"access_token_encryption_alg_values_supported" : [ "none", "RSA1_5", "RSA-OAEP", "A128KW", "A192KW", "A256KW", "dir" ],
"access_token_encryption_enc_values_supported" : [ "none", "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512" ],
"request_object_signing_alg_values_supported" : [ "none", "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" ],
"request_object_encryption_alg_values_supported" : [ "none", "RSA1_5", "RSA-OAEP", "A128KW", "A192KW", "A256KW", "dir" ],
"request_object_encryption_enc_values_supported" : [ "none", "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512" ],
"token_endpoint_auth_methods_supported" : [ "client_secret_post", "client_secret_basic", "client_secret_jwt", "private_key_jwt" ],
"token_endpoint_auth_signing_alg_values_supported" : [ "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" ],
"claims_supported" : [ "preferred_username", "email", "email_verified", "name", "phone_number", "phone_number_verified", "iss", "sub", "aud", "exp", "auth_time", "jti" ],
"ui_locales_supported" : [ "de", "en", "en-us", "es", "fr", "it", "ja", "ko", "nl", "pt-br", "ru", "uk", "zh-cn" ],
"claims_parameter_supported" : true,
"request_parameter_supported" : true,
"request_uri_parameter_supported" : true
}
En esta lista de URLs vemos algunas interesantes como son:
"userinfo_endpoint" : "https://DNS/IP:PUERTO/oauth2/userinfo""token_endpoint" : "https://DNS/IP:PUERTO:57776/oauth2/token""introspection_endpoint" : "https://DNS/IP:PUERTO/oauth2/introspection""registration_endpoint" : "https://DNS/IP:PUERTO/oauth2/register""revocation_endpoint" : "https://DNS/IP:PUERTO/oauth2/revocation"
2.3.1.- token
La URL "userInfo" ya le hemos explicado en este articulo, por lo que a continuación vamos a explicar "token_endpoint"; pues es la que utilizamos en capítulos anteriores para obtener el token.
Para consumir la URL "https://DNS/IP:PUERTO:57776/oauth2/token" deberemos realizar un GET con los siguientes parámetros de configuración:
Spoiler
Params:
Grant_type= "client_credentials"
scope = "/my/scope"
Authorization:
Type: Basic Auth
user: el generado por el servidor de Autorización
password: generada por el servidor de Autorización
Body
Seleccionamos "x-www-form-urlencoded" e informamos con el "clientID"
La respuesta de esta llamada será el Token:
Esta llamada se podrá configurar a través de una operación REST como comentábamos anteriormente.
3.3.2.- introspectionLa siguiente URL que vamos a explicar es "introspection_endpoint". Que es la que nos permite validar un token en el servidor de autorización para verificar su estado y obtener más detalles sobre él (como sus permisos, emisor, usuario asociado, etc.). Este endpoint normalmente se utiliza en arquitecturas donde un recurso protegido requiere verificar la validez de un token antes de otorgar acceso.
Para consumir la URL "https://DNS/IP:PUERTO:57776/oauth2/introspection" deberemos realizar un POST con los siguientes parámetros de configuración:
Spoiler
Headers
El valor del header "Authorization" se obtiene codificando en Base64 la combinación de "client_id" y "client_secret" separados por ":".
Body
En el body, selecciona la opción x-www-form-urlencoded en Postman y agrega los siguientes parámetros:
token: El token que deseas validar.
token_type_hint (opcional): Puedes indicar si el token es un access_token o un refresh_token
El resultado de la llamada será algo así:
{
"active": true,
"scope": "my/scope",
"client_id": "bezkfeZoA3mU2g1dmABlvgv9k1AKHN78JtIgXgcdQeQ",
"username": "",
"token_type": "bearer",
"exp": 1748778056,
"sub": "bezkfeZoA3mU2g1dmABlvgv9k1AKHN78JtIgXgcdQeQ",
"aud": "bezkfeZoA3mU2g1dmABlvgv9k1AKHN78JtIgXgcdQeQ",
"iss": "https://DNS/IP:PUERTO/oauth2"
}
Explicación de los campos:
"active": true: El token es válido.
"scope": "my/scope": Permisos asociados al token.
"client_id": "bezkfeZoA3mU2g1dmABlvgv9k1AKHN78JtIgXgcdQeQ": El cliente que generó el token.
"username": "": (opcional) Usuario asociado al token.
"exp": Tiempo de expiración (en formato UNIX timestamp).
"sub": Identificador único del usuario.
"aud": Audiencias permitidas.
"iss": Emisor del token.
3.3.3.- registration
La siguiente URL que vamos a explicar es "registration_endpoint" . Este endpoint permite que las aplicaciones cliente dinámicamente se registren en el servidor de autorización sin necesidad de configurar manualmente las credenciales en el servidor.
Para consumir la URL "https://DNS/IP:PUERTO:57776/oauth2/register" deberemos realizar un POST con los siguientes parámetros de configuración:
Spoiler
Headers
Informar el campo "Content-Type" con el valor "application/json".
Body
Os dejo el JSON del body en formato texto. Importante el "client_name" para poder localizar en el portal de gestión nuestro usuario recién creado:
{
"redirect_uris": [
"https://client.example.com/callback",
"https://client.example.com/auth"
],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"client_name": "MiCLIENTE",
"token_endpoint_auth_method": "client_secret_basic",
"scope": "my/scope"
}
Tras realizar la llamada veremos la siguiente respuesta con toda la información de creación del usuario:
{
"client_id": "8IFeFdbCiz7CAJ86Bxz4aZUX8s3W-TP3Litmpx4vqOM",
"client_secret": "6nWT1lL4BOcM0tYz8zTvzgtSqiGpDOXyj-3-MZCa2ei9QMWxoTMKM49LdWrk8XlQYXaZpsVxuTnzv924L52NSw",
"registration_access_token": "4v5mCm0w0MP6uA0YnZpSTD0ib-XFYnE4i0vlA9QWC9sas9BsSxH8Mipij5XOUvEv5yDRtXKkHmMMLhZjCd5LHw",
"registration_client_uri": "https://DNS/IP:PUERTO/oauth2/register?client_id=8IFeFdbCiz7CAJ86Bxz4aZUX8s3W-TP3Litmpx4vqOM",
"client_id_issued_at": 1748779129,
"client_secret_expires_at": 0,
"redirect_uris": [
"https://client.example.com/callback",
"https://client.example.com/auth"
],
"response_types": [
"code"
],
"grant_types": [
"authorization_code",
"refresh_token"
],
"application_type": "web",
"client_name": "MiCLIENTE",
"id_token_signed_response_alg": "RS256",
"token_endpoint_auth_method": "client_secret_basic"
}
En el portal de gestión, podemos confirmar la creación del usuario:
Así como confirmar el valor del "clientSecret" y el "clientID" que nos ha devuelto la llamada:
Objetivo conseguido, ¡USUARIO CREADO!
3.3.4.- revocation
La siguiente URL que vamos a explicar es "revocation_endpoint". Que es el endpoint definido en el estándar OAuth 2.0 que permite revocar tokens emitidos previamente por un servidor de autorización
Para consumir la URL "https://DNS/IP:PUERTO:57776/oauth2/revocation" deberemos realizar un POST con los siguientes parámetros de configuración:
Spoiler
Authorization
Informamos "Basic Auth" con nuestro "client_id" y nuestro "secret_client"
Header
Informamos el "Content-Type" con ""application/x-www-form-urlencoded".
Body
Informamos el "token" que queremos revocar y el "token_type_hint" con "access_token".
Finalmente tendremos un HTTP 200 como respuesta de la llamada:
De esta forma quedará el Token revocado.
Con esta revocación del token termina este articulo. Espero que les sea de utilidad y les sirva para ponerse al día en cuanto a aplicación de seguridad en nuestras plataformas, pues todo esto lo podemos gestionar desde nuestro portal de gestión de Intersystems.
Finalmente, espero que se conviertan ¡en el mejor equipo de seguridad posible!
Muchas gracias por el tiempo que han dedicado a esta lectura.
Artículo
Mathew Lambert · 15 feb, 2021
## Introducción y motivación {#RobustErrorHandlingandCleanupinObjectScript-IntroductionandMotivation}
Una unidad de código en ObjectScript (pongamos, un ClassMethod) puede producir una gran variedad de efectos secundarios inesperados cuando interactúa con partes del sistema que están fuera de su propio alcance y no han sido depuradas adecuadamente. En forma de lista parcial, se incluyen:
* Transacciones
* Locks
* Dispositivos E/S
* Cursores SQL
* Flags de systema y configuración
* $Namespace
* Archivos temporales
Utilizar estas importantes funciones del lenguaje sin hacer cleanup adecuadamente y con desarrollo defensivo podría ocasionar que una aplicación, que normalmente funciona correctamente, falle de forma inesperada y sea difícil de debugar. Es fundamental que el código de cleanup funcione correctamente en todos los posibles casos de error, en especial porque es probable que en pruebas superficiales se ignoren los casos de error. En este artículo se detallan varios problemas conocidos, y se explican dos patrones para lograr que los errores se gestionen y eliminen de manera eficiente.
_Injerto importante que está parcialmente relacionado: ¿quieres asegurarte de que estás probando todos tus casos hasta el límite? Echa un vistazo a la ¡[Herramienta de cobertura para pruebas en Open Exchange](https://openexchange.intersystems.com/package/Test-Coverage-Tool)!_
## Trampas a evitar {#RobustErrorHandlingandCleanupinObjectScript-PitfallstoAvoid}
### Transacciones {#RobustErrorHandlingandCleanupinObjectScript-Transactions}
Un enfoque natural y simplista de las transacciones consiste en envolver la transacción en un bloque try/catch, con un TRollback en catch, de la siguiente manera:
Try {
TSTART
// ... do stuff with data ...
TCOMMIT
} Catch e {
TROLLBACK
// e.AsStatus(), e.Log(), etc.
}
Estando aislados, este código siempre que lo escrito entre TStart y TCommit arroje excepciones en vez de producir Quit cuando se produzca un error es perfectamente válido. Sin embargo, es arriesgado por dos razones:
* Si otro desarrollador agrega un “Quit” dentro del bloque Try, una transacción se quedará abierta. Sería sencillo saltarse ese cambio durante la revisión del código, especialmente si no es obvio que haya transacciones involucradas en el contexto actual.
* Si se llama al método con este bloque desde una transacción externa, TRollback restaurará todos los niveles de la transacción.
Un mejor enfoque consiste en rastrear el nivel de la transacción al principio del método y retroceder hasta ese nivel de la transacción al final. Por ejemplo:
Set tInitTLevel = $TLevel
Try {
TSTART
// ... do stuff with data ...
// The following is fine now; tStatus does not need to be thrown as an exception.
If $$$ISERR(tStatus) {
Quit
}
// ... do more stuff with data ...
TCOMMIT
} Catch e {
// e.AsStatus(), e.Log(), etc.
}
While $TLevel > tInitTLevel {
// Just roll back one transaction level at a time.
TROLLBACK 1
}
### Locks {#RobustErrorHandlingandCleanupinObjectScript-Locks}
Cualquier código que utilice locks progresivos también debe garantizar que disminuyan los locks en el código de depuración cuando ya no sean necesarios; de lo contrario, dichos locks se mantendrán hasta que el proceso termine. Los locks no deben filtrarse fuera de un método, a menos que la obtención de dicho lock sea un efecto secundario documentado del método.
### Dispositivos de E/S {#RobustErrorHandlingandCleanupinObjectScript-IODevices}
De forma similar, los cambios en el dispositivo de E/S actual (la variable especial $io) tampoco deberían filtrarse fuera de un método, a menos que el propósito del método sea cambiar el dispositivo actual (por ejemplo, habilitar la redirección de E/S). Cuando se trabaja con archivos, es preferible utilizar el paquete %Stream en vez del E/S para archivos secuenciales directos por medio de OPEN / USE / READ / CLOSE. En otros casos, donde es necesario utilizar dispositivos de E/S, debe restablecerse el dispositivo original cuando finalice el método. Por ejemplo, el siguiente patrón de código es arriesgado:
Method ReadFromDevice(pSomeOtherDevice As %String)
{
Open pSomeOtherDevice:10
Use pSomeOtherDevice
Read x
// ... do complicated things with X ...
Close pSomeOtherDevice
}
Si se lanza una excepción antes de que pSomeOtherDevice esté cerrado, entonces $io se quedará como pSomeOtherDevice; esto probablemente ocasionará errores en cascada. Además, cuando el dispositivo se cierra, $io se restablece al dispositivo predeterminado del proceso, el cual probablemente no sea el mismo dispositivo que se utilizó antes de que se llamara el método.
### Cursores SQL {#RobustErrorHandlingandCleanupinObjectScript-SQLCursors}
Cuando se utiliza SQL basado en cursores, el cursor debe estar cerrado en caso de ocurra cualquier error. Cuando el cursor no se cierra, pueden producirse filtraciones en los recursos (según la documentación de apoyo). Además, en algunos casos, si se ejecuta el código de nuevo y se intenta abrir el cursor, se obtendrá un error “already open” (SQLCODE -101).
### Flags de sistema y configuración {#RobustErrorHandlingandCleanupinObjectScript-SystemFlagsandSettings}
Excepcionalmente, el código de la aplicación puede necesitar que se modifiquen flags a nivel de proceso o del sistema; por ejemplo, muchos están definidos en %SYSTEM.Process y %SYSTEM.SQL . En todos esos casos, debe tenerse cuidado de almacenar el valor inicial y restablecerlo al final del método.
### $Namespace {#RobustErrorHandlingandCleanupinObjectScript-Namespace}
El código que modifica el namespace siempre debe ser New $Namespace al principio, para garantizar que los cambios en el namespace no se filtran fuera del alcance del método.
### Archivos temporales {#RobustErrorHandlingandCleanupinObjectScript-TemporaryFiles}
El código de la aplicación encargado de crear archivos temporales, como %Library.File:TempFilename (que, en InterSystems IRIS, realmente crea el archivo), debería eliminar también los archivos temporales cuando ya no se necesiten.
## Patrón recomendado: Try-Catch (-Finally) {#RobustErrorHandlingandCleanupinObjectScript-TryCatchFinally}
Muchos lenguajes tienen una función en la que una estructura de tipo try/catch también puede tener un bloque “finally” que se ejecuta cuando se ha completado try/catch, tanto si se produjo una excepción como si no. ObjectScript no lo hace, pero puede aproximarse. Un patrón general para esto, que demuestra muchos de los posibles casos problemáticos, es el siguiente:
ClassMethod MyRobustMethod(pFile As %String = "C:\foo\bar.txt") As %Status
{
Set tSC = $$$OK
Set tInitialTLevel = $TLevel
Set tMyGlobalLocked = 0
Set tDevice = $io
Set tFileOpen = 0
Set tCursorOpen = 0
Set tOldSystemFlagValue = ""
Try {
// Lock a global, provided a lock can be obtained within 5 seconds.
Lock +^MyGlobal(42):5
If '$Test {
$$$ThrowStatus($$$ERROR($$$GeneralError,"Couldn't lock ^MyGlobal(42)."))
}
Set tMyGlobalLocked = 1
// Open a file
Open pFile:"WNS":10
If '$Test {
$$$ThrowStatus($$$ERROR($$$GeneralError,"Couldn't open file "_pFile))
}
Set tFileOpen = 1
// [ cursor MyCursor declared ]
&;SQL(OPEN MyCursor)
Set tCursorOpen = 1
// Set a system flag for this process.
Set tOldSystemFlagValue = $System.Process.SetZEOF(1)
// Do the important things...
Use tFile
TSTART
// [ ... lots of important and complicated code that changes data here ... ]
// All done!
TCOMMIT
} Catch e {
Set tSC = e.AsStatus()
}
// Finally {
// Cleanup: system flag
If (tOldSystemFlagValue '= "") {
Do $System.Process.SetZEOF(tOldSystemFlagValue)
}
// Cleanup: device
If tFileOpen {
Close pFile
// If pFile is the current device, the CLOSE command switches $io back to the process's default device,
// which might not be the same as the value of $io was when the method was called.
// To be extra sure:
Use tDevice
}
// Cleanup: locks
If tMyGlobalLocked {
Lock -^MyGlobal(42)
}
// Cleanup: transactions
// Roll back one level at a time up to our starting transaction level.
While $TLevel > tInitialTLevel {
TROLLBACK 1
}
// } // end "finally"
Quit tSC
}
Nota: en este enfoque, es fundamental que se utilice “Quit” y no “Return” en el bloque “Try” ...; “Return” haría un bypass del cleanup.
## Patrón recomendado: Objetos Registrados y Destructores {#RobustErrorHandlingandCleanupinObjectScript-RecommendedPattern:RegisteredObjectsandDestructors}
A veces, el código de depuración puede complicarse. En estos casos, quizás tenga sentido facilitar la reutilización del código de depuración integrándolo en un registered object. El estado del sistema es inicializado cuando se inicializa el objeto o cuando se llama a los métodos del objeto que cambian el estado, y regresa a su valor original cuando el objeto ya no está al alcance. Mira este sencillo ejemplo, que administra las transacciones, el namespace actual y el estado de $System.Process.SetZEOF:
/// When an instance of this class goes out of scope, the namespace, transaction level, and value of $System.Process.SetZEOF() that were present when it was created are restored.
Class DC.Demo.ScopeManager Extends %RegisteredObject
{
Property InitialNamespace As %String [ InitialExpression = {$Namespace} ];
Property InitialTransactionLevel As %String [ InitialExpression = {$TLevel} ];
Property ZEOFSetting As %Boolean [ InitialExpression = {$System.Process.SetZEOF()} ];
Method SetZEOF(pValue As %Boolean)
{
Set ..ZEOFSetting = $System.Process.SetZEOF(.pValue)
}
Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
{
Set tSC = $$$OK
Try {
Set $Namespace = ..InitialNamespace
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Try {
Do $System.Process.SetZEOF(..ZEOFSetting)
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Try {
While $TLevel > ..InitialTransactionLevel {
TROLLBACK 1
}
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Quit tSC
}
}
La siguiente clase demuestra cómo la clase registrada anteriormente podría utilizarse para simplificar la depuración cuando finalice el método:
Class DC.Demo.Driver
{
ClassMethod Run()
{
For tArgument = "good","bad" {
Do ..LogState(tArgument,"before")
Do ..DemoRobustMethod(tArgument)
Do ..LogState(tArgument,"after")
}
}
ClassMethod LogState(pArgument As %String, pWhen As %String)
{
Write !,pWhen," calling DemoRobustMethod("_$$$QUOTE(pArgument)_"):"
Write !,$c(9),"$Namespace=",$Namespace
Write !,$c(9),"$TLevel=",$TLevel
Write !,$c(9),"$System.Process.SetZEOF()=",$System.Process.SetZEOF()
}
ClassMethod DemoRobustMethod(pArgument As %String)
{
Set tScopeManager = ##class(DC.Demo.ScopeManager).%New()
Set $Namespace = "%SYS"
TSTART
Do tScopeManager.SetZEOF(1)
If (pArgument = "bad") {
// Normally, this would be a big problem. In this case, because of tScopeManager, it isn't.
Quit
}
TCOMMIT
}
}
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
(Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Kurro Lopez · 3 jun, 2019
"Telegram" es un popular programa de mensajería instantánea, que proporciona una API para la creación de bots. Las características de esta API le permiten crear bots con una amplia gama de funciones, incluida la recepción de pagos. Con la ayuda del bot de Telegram, resolví una tarea sencilla: enviar alertas desde Ensemble hacia Telegram.Ventajas: Cuando las alertas lleguen a su teléfono móvil, aparecerá una notificación, por lo que no es necesario que instale ninguna aplicación adicional (en contraste con la solución https://community.intersystems.com/post/sending-alerts-mobile-phone-using-pushover- httpoutboundadapter).El bot podrá hacer más cosas al añadir nuevos comandos, por ejemplo, para administrar su Productividad o cuando desee resolver otras tareas en Iris, Ensemble o Cache. Si quieres, puedes empezar a echar un vistazo al código - github projectEn primer lugar, considere algunas de las características de Telegram cuando desee crear un bot :Telegram es un mensajero muy popular debido a la seguridad que brinda. Por ejemplo, cuando un usuario comienza a chatear con un robot, éste no conoce el número de teléfono del usuario. Esto es bueno para el usuario, ya nadie podrá recopilar los números de teléfono. Además, es imposible enviar un mensaje con solo conocer el número de teléfono. Telegram es compatible con 2 maneras de enviar actualizaciones (aquellos mensajes que los usuarios escriben a los robots) hacia su servidor:Long polling. Para recibir actualizaciones de entrada, llame al método de la API después de cumplir con los intervalos de tiempo necesarios.Webhook. Registre su URL, y siempre que haya una actualización para el bot, Telegram enviará una petición HTTPS POST a la URL que se especificó.Ejecuté ambas opciones. Cómo funciona este robot y qué se hace en EnsembleCualquier interacción comienza con el envío del comando /start. La respuesta: será un mensaje de texto con un saludo y una lista de los comandos disponibles El usuario envía el comando /subscribe. La respuesta contiene un botón especial que le permite solicitar el número de teléfono del usuario. Esto es necesario para identificar al usuario y determinar cuáles son los mensajes que se le deben enviar. Al hacer clic en este botón el usuario confirma la autorización para enviar su número de teléfono. Ensemble verifica si este número se encuentra en el registro de empleados. Si es así, se guarda el número de identificación del chat. Cuando se produce un error en alguno de los componentes de producción de Ensemble, este automáticamente crea un mensaje de AlertRequest y lo envía a la operación empresarial Ens.Alert. Con la operación empresarial Ens.Alert busca a todos los empleados que tienen un número de identificación en el chat y les envía un mensaje sobre el problema a cada uno de ellos. El comando /alert (que envía la prueba Alert) también se proporciona para que lo pruebe. FuncionesPara almacenar el vínculo del número de teléfono del empleado y el número de identificación del chat en Ensemble, elegí la siguiente tabla. Las claves son los números de teléfono, los valores son los números de identificación del chat de Telegram. Cuando un usuario se registra para recibir notificaciones, el número de identificación del chat se actualiza.Para recibir las actualizaciones, primero se implementó el mecanismo Long poll. Hacer esto es muy sencillo en Ensemble, basta con añadir un servicio (Business Service) y especificar el intervalo de llamadas. Después de especificar el intervalo, Ensemble accederá a la API de Telegram para obtener las actualizaciones. La desventaja de este enfoque, es que el usuario puede notar un retraso entre el envío del comando y la respuesta del bot. Un punto a favor, es que no necesita proporcionar acceso al servidor de Ensemble desde Internet. La implementación de webhook no es un problema, ya que la manera de crear un servicio REST JSON en Cache se ha descrito muchas veces y en detalle. Pero al implementar webhook, debe tener en cuenta que Telegram sólo envía mensajes a través de HTTPS, por lo que debe configurar SSL en su servidor web (utilicé un certificado firmado por mi mismo) .La secuencia de pasos para añadir una función específica en su producciónCree un bot con la ayuda de otro robot especial "BotFather"https://telegram.me/BotFather cuando lo haga recibirá un token Es necesario que cree una configuración de cliente SSL en Ensemble, ya que el mensaje se envíará a través de HTTPS. Importe las clases desde https://github.com/intersystems-community/TelegramAlerts (un paquete de Telegram):API.cls - métodos de implementación de las clases para trabajar con la API.TelegramOutboundAdapter.cls - adaptador de salidaTelegramOperation.cls - operación (Business Operation) para enviar mensajes a Telegram TelegramInboundAdapter.cls - adaptador de entradaTelegramService.cls - servicio (Business Service)Msg.TextRequest.cls, Msg.ButtonRequest.cls - clases de mensajesAlertOperation.cls - operación (Business operation) para enviar alertasRESTBroker.cls - REST-broker para webhook Genere una tabla de búsqueda en el portal(https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=ECONFIG_other#ECONFIG_other_lookup_tables) y añada una o más filas en él, además utilice un número de teléfono (sin +) como clave, con los números de teléfono de aquellos que necesiten recibir notificaciones. Añada y configure la operación empresarial TelegramOperation.cls a la producción. En su configuración, debe especificar TelegramToken y SSLConfigurationPara recibir notificaciones, debe realizar los pasos 6 ó 7. Si utiliza long polling, añada y configure el servicio (Business Service) de TelegramService a su producción. Para ello debe especificar a TelegramToken, SSLConfiguration y TableName (busque el nombre de la tabla). Configure el intervalo de llamadas. Si utiliza webhook:Cree una aplicación web y especifique la clase Telegram.RESTBroker.cls como un bróker.Configure SSL en el servidor web.Registre webhook, para esto necesita llamar al método SetWebhook de la clase Telegram.APIAñada el servicio (Business Service) TelegramService a su producción, especifique el parámetro TableName. Especifique a poolsize = 0. Añada AlertOperation.cls con el nombre Ens.Alert y configure los parámetros: TelegramToken, SSLConfiguration, TableName. ¡Listo!
Artículo
Nancy Martínez · 3 jul, 2020
El objetivo de esta "Guía para solucionar problemas en DeepSee" es ayudar a localizar y solucionar los problemas en un proyecto en DeepSee.
Si el problema no puede solucionarse siguiendo estas recomendaciones, al menos tendrás suficiente información para reportar el problema al Centro de Soporte Internacional (WRC) y proporcionarnos todas la información, para que podamos continuar la investigación juntos y resolverlo más rápido!
Nota.- Si no se está familiarizado con las consecuencias de una determinada acción o comando, no se deben ejecutar, ya que esto podría tener algún efecto en el rendimiento del sistema. En este caso, lo aconsejable es ponerse en contacto con el soporte técnico de DeepSee para obtener más ayuda.
La forma más sencilla de seguir esta guía es comenzar por el lado izquierdo y llegar hasta la columna de Soluciones que se encuentra a la derecha.
.deepsee-solutions td,
.deepsee-solutions th {
padding: 5px;
vertical-align: top;
text-align: left;
font-size: 12px;
}
Tipo de problema
¿Qué ocurrió?
Análisis
Solución
Problemas con la compilación
Errores durante la compilación
Comprobar el mensaje de errorEjecutar $System.OBJ.DisplayError() si la compilación no mostró erroresRevisar ^DeepSee.BuildErrors/ run ##class(%DeepSee.Utils).%PrintBuildErrors(pCube)
Corregir los errores que aparecen en el mensaje
Se crearon menos registros que filas en la tabla de origen
Verificar el modelo analítico para crear restricciones
Eliminar o aceptar las restricciones de compilación que se generen
Comprobar si se utilizaron maxfacts
Eliminar maxfacts
Comprobar ^DeepSee.BuildErrors/ run ##class(%DeepSee.Utils).%PrintBuildErrors(pCube)
Corregir los errores de compilación que se produzcan
Revisar los índices en la clase de origen
Reconstruir los índices en la clase de origen
Faltan datos
Los registros del origen no están disponibles en DeepSee
Consultar las sección anterior para problemas / restricciones durante la compilación
Eliminar las restricciones
Verificar "%OnProcessFact" en la definición del modelo analítico
Corregir el método, si es necesario
Comprobar si hay errores en la compilación
Reparar los errores que se produzcan
Depurar los métodos en sourceExpression
Reparar los métodos
Revisar los registros de la tabla Fact
Encontrar por qué no coinciden y resolver el problema
Comparar la tabla Fact con la tabla de origen
Resultados incorrectos
Miembros duplicados en el nivel
Verificar la dimensión para jerarquía válida
Cambiar el nivel según este artículo
La consulta muestra resultados inesperados
Verificar la sección anterior si faltan datosComprobar si hay problemas de almacenamiento en caché: - do $System.DeepSee.Shell() - desactive caché
Corregir los errores en la compilacióndo $System.DeepSee.Reset()kill ^DeepSee.Cache
Dividir la consulta en segmentos más pequeños (por ejemplo, cada eje por separado)Encontrar los segmentos problemáticos
Reparar los problemas en el segmento
Comprobar ^DeepSee.AgentLog
Ejecutar do ##class(%DeepSee.TaskMaster).%Reset()
Las listas están vacías
Comprobar que las listas tengan el formato habitual
Otorgar permisos para seleccionar desde la tabla de origen
Comprobar si las listas de SQL están personalizadas
Depurar los condicionales WHEREComprobar ^DeepSee.SQLErrorComprobar ^DeepSee.QueryLog
Caídas del software / Eventos inesperados
DeepSee no responde como se espera
Verificar el estado del sistema
Caídas del software
Comprobar el estado del agente
Revisar el uso de la licencia
Ampliar la licencia
Fallo en las operaciones del sistema
Comprobar ^DeepSee.AgentLog
Ejecutar do ##class(%DeepSee.TaskMaster).%Reset()
Ejecute do ##class(%DeepSee.TaskMaster).%PrintLog()
Comprobar ^DeepSee.LastLogErrorComprobar ^DeepSee.PivotError()
Reparar los errores que se encontraron
Problemas con el rendimiento
Bajo rendimiento en la compilación
Ejecutar do ##class(%DeepSee.TaskMaster).%PrintLog()
Verificar el número de agentes disponibles
Ejecutar do ##class(%DeepSee.TaskMaster).%Reset()
Verificar el uso de memoria y CPU
Ejecutar la compilación en el modo de carga baja
Comprobar expresiones de origen
Evitar las expresiones de origen, si es posible
Verifique las estadísticas generales del Cubo:
Eliminar las dimensiones/niveles que no sean necesarios
- Ejecutar do ##class(%DeepSee.Utils).%Analyze("Holefoods")
Mantener los cubos simples y pequeños
La actividad del registro es muy alta
Al utilizar un diseño para el almacenamiento de datos o un namespace separado para DeepSee, se puede desactivar el registro en este namespace
Bajo rendimiento en las consultas
Ejecute el informe: do ##class(%DeepSee.Diagnostic.MDXUtils).%Run(<query>)
Las consultas que posiblemente se realicen a largo plazo deberán simplificarse mediante algún método alternativo
Verifique el uso de memoria y CPU
Liberar los recursos
Comprobar la configuración del buffer
Incrementar los buffers, si es posible
Realizar un análisis general sobre el rendimiento del sistema
En problemas relacionados con el rendimiento durante el tiempo de ejecución, solicite ayuda al WRC
Problemas más frecuentes
El Namespace no aparece en el menú DS
Compruebe que DeepSee esté habilitado en la configuración de la aplicación web /csp/<namespace>
Habilitar "DeepSee" en la aplicación web
La opción Architect aparece en gris
Revisar la licencia
obtener la licencia habilitada para DeepSee
Verificar los roles del usuario
Agregar el recurso %DeepSee_Architect / %DeepSee_ArchitectEdit Use
Comprobar que su navegador no sea Internet Explorer 8
Utilizar un navegador compatible
Estos son los diagramas para cada tipo de problemas:
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
(Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Joel Espinoza · 28 oct, 2019
¡Hola Comunidad!
Una función útil de nuestra estructura REST es la capacidad que tienen las clases de Dispatch para identificar los prefijos de una solicitud y redireccionarlos a otra clase de Dispatch. Este enfoque permite mejorar el orden y la lectura del código, permite mantener separadas las versiones de una interfaz fácilmente y ofrece una forma de proteger llamadas a APIs a las que solo ciertos usuarios podrán acceder.
Resumen
Para configurar un servicio REST en su instancia de Caché o IRIS Database, necesita definir una aplicación CSP dedicada y crear una clase de Dispatch asociada que gestione las solicitudes que entran. La clase de Dispatch se extiende desde %CSP.REST e incluirá un bloque XData que contiene su mapa URL. Esto indica al sistema qué método deberá llamar cuando reciba una solicitud en particular.
Por ejemplo:
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/orders" Method="GET" Call="GetOrders"/>
<Route Url="/orders" Method="POST" Call="NewOrder"/>
</Routes>
}
Los elementos <Route> determinan las diferentes solicitudes que administrará el servicio. Una solicitud GET para el recurso "/orders" llamará al método de clase "GetOrders". Una solicitud POST que se realiza al mismo recurso llamará, en su lugar, al método "NewOrder".
Es importante tener en cuenta que el nombre de la aplicación CSP no se considera parte del nombre del recurso solicitado en nuestro mapa URL. Analice una solicitud realizada en la dirección:
http://localhost:57772/csp/demo/orders
Si nuestra aplicación CSP se llama "/csp/demo", entonces el único segmento de la solicitud que es administrado por la clase de Dispatch es el que se encuentra después del nombre de la aplicación. En este caso es "/orders".
Reenvío de solicitudes
En lugar de llamar a un método desde dentro de la clase de Dispatch, la otra opción para su mapa URL es reenviar todas las solicitudes que coincidan con un prefijo en particular hacia una clase de Dispatch diferente.
Esto se realiza mediante el elemento <Map> que se encuentra en la sección UrlMap. El elemento contiene dos atributos, Prefix y Forward. Si la solicitud de la URL coincide con alguno de los prefijos, entonces, enviamos la solicitud a la clase de Dispatch especificada, para que se procese posteriormente.
Por ejemplo:
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Map Prefix="/shipping" Forward="Demo.Service.Shipping"/>
<Route Url="/orders" Method="GET" Call="GetOrders"/>
<Route Url="/orders" Method="POST" Call="NewOrder"/>
</Routes>
}
Una solicitud GET o POST para "/orders" será administrada directamente por esta clase. Sin embargo, las solicitudes que coincidan con el prefijo "/shipping" se redireccionarán a la clase de Dispatch Demo.Service.Shipping, que tiene su propio mapa URL:
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/track/:id" Method="GET" Call="TrackShipment"/>
</Routes>
}
Fallo en el enrutamiento de la URL
Para demostrar cómo influye cada componente solicitado por la URL en el método que se llama al final, analizaremos cada elemento de una solicitud para la siguiente dirección:
http://localhost:57772/csp/demo/shipping/track/123
http://
Es el protocolo que se usó para la solicitud.
localhost:57772 (52773 en IRIS)
Es el servidor al que nos conectamos.
/csp/demo/shipping/track/123
Es el recurso que se está solicitando.
/csp/demo
Es el nombre de la aplicación CSP.Una clase de Dispatch se define para la aplicación, y enruta la solicitud hacia allí.
/shipping/track/123
Es el segmento del recurso que se enviará a la primera clase de Dispatch.
/shipping
Es el prefijo que coincide con el elemento <Map> en el mapa URL.Lo redireccionará a la clase Demo.Service.Shipping.
/track/123
Es el segmento del recurso que se envió a la segunda clase de Dispatch.Coincide con la ruta "/track/:id".Llama al método TrackShipment(123).
Resultados
Control del código fuente — Separar su API REST en varias clases reducirá el tamaño total de cada clase, lo cual le ayudará a mantener el historial del código fuente bajo control, conciso y legible.
Versiones — Una manera sencilla para lograr que varias versiones de una API sean compatibles simultáneamente es utilizar el reenvío. Una sola clase de Dispatch podría reenviar solicitudes que coincidan con los prefijos /v1 o /v2 a una clase de Dispatch que implemente esa versión de la API. La API REST, que es un elemento central de Atelier, nuestro nuevo IDE, utiliza el mismo esquema de versiones.
Seguridad — Si su API necesita tener rutas que estén restringidas para ciertos usuarios - por ejemplo, para permitir que únicamente los administradores realicen ciertos tipos de solicitudes- tiene sentido aislar estas rutas en su propia clase y después reenviar las solicitudes mediante un prefijo en particular. Si la segunda clase de Dispatch define un método OnPreDispatch, su código se ejecutará antes del procesamiento de cada solicitud. El servicio puede utilizar esto para comprobar los privilegios de un usuario y decidir si continúa con el procesamiento o cancela su solicitud.
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
(Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Mathew Lambert · 15 jul, 2020
¡Hola desarrolladores!
Aquí podéis ver el anuncio del proyecto isc-tar de @Dmitriy Maslennikov. En ocasiones, la historia de porque se ha llegado a un resultado es igual o más interesante que el resultado: cómo se construyó, cómo funciona y qué sucede en torno al proyecto. Esta es la historia:
Cómo desarrollar este proyecto
Cómo probarlo
Cómo lanzar nuevas versiones para publicar
Cómo automatizar todo lo anterior
Integración continua
Os hablaré de todo eso.
Desarrollo
Últimamente, mis herramientas favoritas son Docker y VSCode. Uso Docker para empezar cualquiera de mis proyectos en su propio entorno y VSCode es sencillamente el mejor editor existente, el cual uso junto con la extensión vscode-objectscript. En cualquier nuevo proyecto, uso por lo menos esas dos herramientas. También soy usuario de macOS, por lo que todos los siguientes pasos fueron probados y deberían funcionar bien en macOS y quizás también en Linux, pero podría ser distinto para Windows.
Antes de nada, clonamos nuestro proyecto en algún sitio
$ git clone git@github.com:daimor/isc-tar.git
Yo tengo el código de comando en mi intérprete, para poder abrir fácilmente cualquier carpeta desde mi terminal con el código. Si quieres esta funcionalidad, puedes configurarla desde la consola de comandos. Para que quede disponible el comando, tendrás que actualizar tu intérprete
Abre el editor solo con el repositorio clonado
$ code isc-tar
VSCode tiene la opción de un terminal integrado. Ahora puedes abrirlo sin perder tu terminal, que estará muy cerca siempre. Podemos usar el terminal para configurar el entorno del proyecto con los dos comandos siguientes:
$ docker-compose build --no-cache
$ docker-compose up -d
esto puede tardar un poco.
Y ya puedes escribir código. Este repositorio contiene la configuración para VSCode, por lo que ya está preconfigurado para usar este IRIS
Pruebas
El proyecto tiene un par de pruebas que puedes realizar:
en el terminal, ve a la sesión de IRIS
$ docker-compose exec iris iris session iris
USER>do ##class(%UnitTest.Manager).RunTest()
Por cierto, ¿has notado los 3 "iris" en el comando? A mí no me gusta tener que repetir las cosas, y hay que escribir "iris" tres veces para lanzar el intérprete de IRIS. La primera vez es para el nombre del servicio en docker-compose, la segunda es el comando iris dentro del contenedor (que sustituye a ccontrol que se usaba en Caché), y la tercera es para el nombre de la instancia. Me gustaría que InterSystems añadiera un comando session más corto, para eliminar esta innecesaria repetición de la palabra "iris". Debería ser algo como esto
docker-compose exec iris session
Publicación
Como se comentó en el anuncio, esta herramienta también podría funcionar en versiones anteriores de Caché y Ensemble. Pero no todas esas versiones soportan UDL como formato de importación. Por lo tanto, necesitamos XML, que se puede usar siempre, tanto en IRIS como en Caché/Ensemble. Afortunadamente, tengo una sola clase y puedo hacer una exportación simple.
do $system.OBJ.Export("%zUtils.FileBinaryTar.cls", "/opt/zUtils.FileBinaryTar.xml", "/diffexport")
Y lamentablemente, por algún motivo desconocido, no puedo usar un útil qualifier /exportversion=2010.1
USER>do $system.OBJ.Export("%zUtils.FileBinaryTar.cls", "/opt/zUtils.FileBinaryTar.xml", "/diffexport/exportversion=2010.1")
Exporting to XML started on 03/16/2019 09:18:35
Exporting class: %zUtils.FileBinaryTar
ERROR #5126: XML export version '2010.1' not supported, supports 2010.1 and onwards.
Errors detected during export.
En el XML exportado, necesito sustituir el encabezado Export para que también pueda reconocerlo Caché.
<Export generator="IRIS" version="26">
Y para eso uso el comando sed.
sed -i.bak 's/^<Export generator="IRIS" .*$/<Export generator="Cache" version="25">/g' /opt/zUtils.FileBinaryTar.xml
Ahora se puede usar tanto en Caché como en IRIS.
Automatización
Para ejecutar pruebas y compilar versiones, es necesario recordar e invocar comandos largos, por lo que sería bueno simplificarlo lo máximo posible. Y Makefile es perfectamente adecuado para ello.
APP_NAME = isc-tar
IMAGE ?= daimor/$(APP_NAME)
SHELL := /bin/bash
.PHONY: help build test release
help: ## This help.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
.DEFAULT_GOAL := help
build: ## Build the image
docker build -t $(IMAGE) .
test: build ## Run UnitTests
docker run --rm -i -v `pwd`/tests:/opt/tests -w /opt --entrypoint /tests_entrypoint.sh $(IMAGE)
release: clean build ## Export as XML
docker run --rm -i -v `pwd`/out:/opt/out -w /opt --entrypoint /build_artifacts.sh $(IMAGE)
clean:
-rm -rf out
Mi Makefile es bastante simple. Tengo dos recetas principales
make test - ejecuta Unit Tests
make release - exporta el proyecto como XML
Estas dos recetas dependen de build, que compila la imagen de IRIS con el proyecto dentro.
Integración continua
Por supuesto, el artículo no estaría completo sin la integración continua, en breve se publicará la segunda parte, y estoy seguro de que os sorprenderá cómo se hice.
Artículo
Alberto Fuentes · 22 abr, 2022
Durante una actualización a una versión principal (major) es aconsejable recompilar las clases y rutinas de todos tus namespaces (ver Tareas tras la instalación de una versión major).
do $system.OBJ.CompileAllNamespaces("u")
do ##Class(%Routine).CompileAllNamespaces()
Para automatizar esta tarea de administración y mantener un registro de cualquier error, os muestro un ejemplo de una clase para importar y compilar en el namespace USER, que puedes usar después de cada actualización: admin.utils.cls
Class
Class admin.utils.cls
Class admin.utils
{
ClassMethod upgrade(verbose As %Boolean = 0) As %Status
{
set ns=$Namespace
// Stopping all productions
do ..stopAllProductions(verbose)
zn "%sys"
kill ^[ns]upgradeLog
// UpgradeAll
set ^[ns]upgradeLog("UpgradeAll")=$zdt($now(),3,,6)
set start=$zh
do $system.OBJ.UpgradeAll(,.errorLogUpgrade)
do ##class(%SYSTEM.OBJ).UpgradeAll()
set ^[ns]upgradeLog("UpgradeAll","duration")=$zh-start
merge ^[ns]upgradeLog("UpgradeAll","errors")=errorLogUpgrade
// Compile Classes in All Namespaces
set ^[ns]upgradeLog("classes")=$zdt($now(),3,,6)
set start=$zh
do $system.OBJ.CompileAllNamespaces("cbk",.errorLogClasses
set ^[ns]upgradeLog("classes","duration")=$zh-start
merge ^[ns]upgradeLog("classes","errors")=errorLogClasses
// Compile Routines in All Namespaces
set ^[ns]upgradeLog("routines")=$zdt($now(),3,,6)
set start=$zh
do ##Class(%Library.Routine).CompileAllNamespaces(,,.count,.errorLogRoutines)
set ^[ns]upgradeLog("routines","duration")=$zh-start
merge ^[ns]upgradeLog("routines","errors")=errorLogRoutines
merge ^[ns]upgradeLog("routines","count")=coun
// Starting all productions
do ..startAllProductions(verbose)
zn ns
return $$$OK
}
ClassMethod startAllProductions(verbose As %Boolean = 0) As %Status
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=2 {
write:verbose " starting production "_prodname,!
set sc=##class(Ens.Director).StartProduction()
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
ClassMethod stopAllProductions(verbose As %Boolean = 0) As %Status
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=1 {
write:verbose " stopping production "_prodname,!
set sc=##class(Ens.Director).StopProduction(0,1)
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
ClassMethod restartAllProductions(verbose As %Boolean = 0) As %Statu
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=1 {
write:verbose " restarting production "_prodname,!
set sc=##class(Ens.Director).RestartProduction(0,1)
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
ClassMethod cleanAllProductions(verbose As %Boolean = 0) As %Status
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=3 {
write:verbose " cleaning production "_prodname,!
set sc=##class(Ens.Director).CleanProduction(1)
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
}
Después de la actualización, simplemente ejecuta el método admin.utils.upgrade desde una sesión de terminal de IRIS:
USER>do ##class(admin.utils).upgrade()
Y puedes ver los resultados desde el portal de administración a través del explorador System > Globals > upgradeLog Este artículo está etiquetado como "Mejores prácticas" ("Best practices").
Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.
Artículo
Muhammad Waseem · 6 abr, 2022
¡Hola Comunidad!
Esta publicación es una introducción a mi aplicación iris-globals-graphDB en Open Exchange.
En este artículo, mostraré cómo guardar y recuperar Graph Data en InterSystems Globals con la ayuda del framework Python Flask Web y la librería PYVIS Interactive network visualizations.
Recomendación
Leer la documentación relacionada: Using Globals
Introducción al SDK nativo
PYVIS Librería de visualización interactiva de redes
Paso 1: Establecer conexión con IRIS Globals mediante el SDK nativo de Python
#create and establish connection
if not self.iris_connection:
self.iris_connection = irisnative.createConnection("localhost", 1972, "USER", "superuser", "SYS")
# Create an iris object
self.iris_native = irisnative.createIris(self.iris_connection)
return self.iris_native
Paso 2: Guardar datos en globals mediante la función iris_native.set( )
#import nodes data from csv file
isdefined = self.iris_native.isDefined("^g1nodes")
if isdefined == 0:
with open("/opt/irisapp/misc/g1nodes.csv", newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
self.iris_native.set(row["name"], "^g1nodes", row["id"])
#import edges data from csv file
isdefined = self.iris_native.isDefined("^g1edges")
if isdefined == 0:
with open("/opt/irisapp/misc/g1edges.csv", newline='') as csvfile:
reader = csv.DictReader(csvfile)
counter = 0
for row in reader:
counter = counter + 1
#Save data to globals
self.iris_native.set(row["source"]+'-'+row["target"], "^g1edges", counter)
Paso 3: Transferir datos de nodos y bordes a PYVIS desde globals mediante la función iris_native.get()
#Get nodes data for basic graph
def get_g1nodes(self):
iris = self.get_iris_native()
leverl1_subscript_iter = iris.iterator("^g1nodes")
result = []
# Iterate over all nodes forwards
for level1_subscript, level1_value in leverl1_subscript_iter:
#Get data from globals
val = iris.get("^g1nodes",level1_subscript)
element = {"id": level1_subscript, "label": val, "shape":"circle"}
result.append(element)
return result
#Get edges data for basic graph
def get_g1edges(self):
iris = self.get_iris_native()
leverl1_subscript_iter = iris.iterator("^g1edges")
result = []
# Iterate over all nodes forwards
for level1_subscript, level1_value in leverl1_subscript_iter:
#Get data from globals
val = iris.get("^g1edges",level1_subscript)
element = {"from": int(val.rpartition('-')[0]), "to": int(val.rpartition('-')[2])}
result.append(element)
return result
Paso 4: Usar PYVIS Javascript para generar datos gráficos
<script type="text/javascript">
// initialize global variables.
var edges;
var nodes;
var network;
var container;
var options, data;
// This method is responsible for drawing the graph, returns the drawn network
function drawGraph() {
var container = document.getElementById('mynetwork');
let node = JSON.parse('{{ nodes | tojson }}');
let edge = JSON.parse('{{ edges | tojson }}');
// parsing and collecting nodes and edges from the python
nodes = new vis.DataSet(node);
edges = new vis.DataSet(edge);
// adding nodes and edges to the graph
data = {nodes: nodes, edges: edges};
var options = {
"configure": {
"enabled": true,
"filter": [
"physics","nodes"
]
},
"nodes": {
"color": {
"border": "rgba(233,180,56,1)",
"background": "rgba(252,175,41,1)",
"highlight": {
"border": "rgba(38,137,233,1)",
"background": "rgba(40,138,255,1)"
},
"hover": {
"border": "rgba(42,127,233,1)",
"background": "rgba(42,126,255,1)"
}
},
"font": {
"color": "rgba(255,255,255,1)"
}
},
"edges": {
"color": {
"inherit": true
},
"smooth": {
"enabled": false,
"type": "continuous"
}
},
"interaction": {
"dragNodes": true,
"hideEdgesOnDrag": false,
"hideNodesOnDrag": false,
"navigationButtons": true,
"hover": true
},
"physics": {
"barnesHut": {
"avoidOverlap": 0,
"centralGravity": 0.3,
"damping": 0.09,
"gravitationalConstant": -80000,
"springConstant": 0.001,
"springLength": 250
},
"enabled": true,
"stabilization": {
"enabled": true,
"fit": true,
"iterations": 1000,
"onlyDynamicEdges": false,
"updateInterval": 50
}
}
}
// if this network requires displaying the configure window,
// put it in its div
options.configure["container"] = document.getElementById("config");
network = new vis.Network(container, data, options);
return network;
}
drawGraph();
</script>
Paso 5: Llamar a los códigos anteriores desde el archivo principal app.py
#Mian route. (index)
@app.route("/")
def index():
#Establish connection and import data to globals
irisglobal = IRISGLOBAL()
irisglobal.import_g1_nodes_edges()
irisglobal.import_g2_nodes_edges()
#getting nodes data from globals
nodes = irisglobal.get_g1nodes()
#getting edges data from globals
edges = irisglobal.get_g1edges()
#To display graph with configuration
pyvis = True
return render_template('index.html', nodes = nodes,edges=edges,pyvis=pyvis)
Espero que os resulte útil.