Limpiar filtro
Artículo
Nancy Martínez · 20 ene, 2020
¡Hola Comunidad!
HealthShare utiliza muchas transformaciones XSL. Estas transformaciones se utilizan para convertir los documentos médicos de la iniciativa “Integración de las Empresas Sanitarias (IHE)” en SDA (formatos interno de HealthShare), y convertirlos nuevamente en los formatos IHE, con el fin de crear resúmenes de los informes y para lidiar con los perfiles en el IHE (por ejemplo, consultar la información de los pacientes, proporcionar documentos y registrarlos). Los clientes pueden configurar las XSLT para personalizar los informes, o para utilizarlas de alguna otra manera.
Para la depuración y el desarrollo es muy conveniente que se pueda ejecutar un XSLT desde el Terminal.
El método de clase
A continuación, se muestra una clase que contiene un método de clase, que permitirá ejecutar un XSLT desde el terminal en Windows. Previamente, se debe crear la clase en HSREGISTRY o en algún otro namespace en HealthShare (no utilizar HSLIB o VIEWERLIB), y procede a la compilación.
Class Local.XsltTransformer Extends %RegisteredObject
{
ClassMethod Transform(XslDirectory As %String, XslBaseFilename As %String, Directory As %String, InputFilename As %String, OutputFilename As %String, byref Parameters = "")
{
// Run the XSLT transform with the base filename XslBaseFilename (i.e., without the .xsl
// extension) that is in the XslDirectory. Run it on the input file with name InputFilename
// and put the output in the file with name OutputFilename. The input file must be in the
// directory Directory, and the output will be put in the same directory. The Parameters
// argument may be used to pass parameters to the transform (rarely needed). This class
// method should be run from Terminal in a HealthShare namespace other than HSLIB or
// VIEWERLIB. The method will write out the path and name of the transform and any error
// messages.
set In = ##class(%Stream.FileCharacter).%New()
set In.Filename = Directory _ "\" _ InputFilename
set Out = ##class(%Stream.FileCharacter).%New()
set Out.Filename = Directory _ "\" _ OutputFilename
set Transformer = ##class(HS.Util.XSLTTransformer).%New()
set Transformer.XSLTCacheMode = "N"
set Transformer.XSLTDirectory = XslDirectory
write !, XslDirectory _ "\" _ XslBaseFilename _ ".xsl"
set Status = Transformer.Transform( .In, XslBaseFilename _ ".xsl", .Out, .Parameters )
if $system.Status.IsOK( Status ) {
set Status = Out.%Save()
}
if $system.Status.IsError( Status ) {
write $system.Status.GetErrorText( Status )
}
}
}
Los siguientes son los parámetros del método de la clase Transformar: XslDirectory es el directorio donde está el archivo XSLT. La clase primero intentará añadir \Custom al directorio cuando busque la transformación, después intentará hacerlo sin él. XslBaseFilename es el nombre del archivo para la transformación XSL, pero sin la extensión .xsl. Directory es el directorio en el que se encuentra el documento de entrada y donde se quiere que la transformación transcriba los datos de salida de dicha transformación. InputFilename es el nombre del archivo del documento de entrada, incluida su extensión. OutputFilename es el nombre del archivo del documento de salida, incluida su extensión. Los parámetros pueden utilizarse para pasar parámetros la transformación, pero pocas veces son necesarios.
Cómo ejecutar la transformación en el terminal
Para ejecutar una transformación XSL, abre el Terminal, cambia el namespace y ejecuta el método Transformar. Por ejemplo, vamos a ejecutar la transformación de CCDA a SDA que viene con HealthShare. Tengo instalado a HealthShare en C:\InterSystems\HealthShare2016.1.1. Pondré un CCDA llamado "Sample_CCDA.xml" en mi carpeta c:\Junk.
USER>zn "hsregistry"
HSREGISTRY>do ##class(Local.XsltTransformer).Transform( "C:\InterSystems\HealthShare2016.1.1\CSP\xslt\SDA3", "CCDA-to-SDA", "c:\Junk", "Sample_CCDA.xml", "SDA_Out.xml" )
C:\InterSystems\HealthShare2016.1.1\CSP\xslt\SDA3\CCDA-to-SDA.xsl
HSREGISTRY>
El archivo de salida SDA_Out.xml ahora se encuentra en c:\Junk.
Se debe tener en cuenta que no se debe utilizar las transformaciones que se encuentran en el directorio CSP\xslt\SDA. Se debe utilizar las transformaciones que se encuentran en el directorio CSP\xslt\SDA3. Las transformaciones que se encuentran en el directorio SDA son para una versión antigua de SDA (la versión 2).
Asistente para transformaciones Studio XSL
Una alternativa a ejecutar tus transformaciones desde el Terminal es utilizar el Asistente para transformaciones Studio XSL. En Studio, selecciona la ruta Herramientas > Complementos > Asistente para transformaciones XSL. Introduce el archivo de entrada en el campo "XML File" y la transformación XSLT en el campo "XSL File". Para el campo "XSLT Helper Class", seleccione "HSREGISTRY" y "HS.Util.XSLTHelper". Hacer clic en "Finalizar". El resultado se visualizará en la ventana de diálogo (podrás copiarlo y pegarlo):
Depuración de errores
Cuando realices la depuración de errores en las transformaciones XSL, un método para depurar los archivos es añadir elementos <xsl:comment> a las XSLT para que puedas observar varios elementos en la salida. Estos son algunos ejemplos:
<xsl:comment>useFirstTranslation <xsl:value-of select="$useFirstTranslation"/>.
referenceValue <xsl:value-of select="$referenceValue"/>.
displayName <xsl:value-of select="@displayName"/>.
originalText <xsl:value-of select="hl7:originalText/text()"/>.
descriptionValue <xsl:value-of select="$descriptionValue"/>.
</xsl:comment>
<xsl:comment>Context node:</xsl:comment>
<xsl:copy-of select="." />
<xsl:comment>End of context node.</xsl:comment>
Varios de los XSLT de HealthShare envían una solicitud a una plantilla de "Canonización". Por ejemplo, CCDA-to-SDA.xsl esto se hace cuando veas el comentario "Canonizar la salida SDA". La canonización eliminará los comentarios desde la salida, por ello es posible que desee realizar los comentarios durante la depuración.
Documentación
Para obtener más información sobre los XSLT en HealthShare, puedes consultar los siguientes capítulos del libro "Descripción general de Health Connect (Overview of Health Connect)", los cuales se encuentran disponibles en los documentos:
Capítulo 6: Documentos CDA y Transformaciones XSL en HealthShare
Capítulo 7: Personalización del CDA y transformaciones en XSL
En particular, en el Capítulo 7 se proporcionan algunas sugerencias para realizar la depuración. Además, puedes consultar el siguiente capítulo del libro "Registros de los intercambios de información":
Capítulo 14: Gestión de los tipos de informes de resumen en XML
Artículo
Pierre-Yves Duquesnoy · 10 mar, 2021
Con el lanzamiento de [PEX](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EPEX) en InterSystems IRIS 2020.1 e InterSystems IRIS for Health 2020.1, los clientes tienen una mejor forma de incorporar Java en las producciones que con el Java Business Host. PEX proporciona un completo conjunto de APIs para construir la interoperabilidad de los componentes y está disponible tanto en Java como en .NET. Java Business Host ha sido discontinuado y se retirará en una versión futura.
Ventajas de PEX
* Permite que los desarrolladores creen cualquier componente de producción tanto en Java como en .NET
* Se pueden transferir estructuras de mensajes más complejas entre los componentes
* Configuración simplificada
* Workflow de desarrollo simplificado, sin necesidad de ObjectScript.
El resto de este artículo se centra en cómo migrar el código existente de Java Business Host a PEX.
## Resumen
Las clases y las interfaces que utiliza PEX son diferentes a las de Java Business Host (JBH). Aquí ofrecemos un resumen de las diferencias, pero la [documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EPEX_apiref) recoge toda la información necesaria.
* [Cómo cambiar un Business Service](#Converting-a-Business-Service-from-Java-Business-Host-to-PEX)
* [Cómo cambiar una Business Operation](#Converting-a-Business-Operation-from-Java-Business-Host-to-PEX)
* [Configuración](#Settings)
* [Mensajes](#Messages)
* [Logging](#Logging)
## Cómo cambiar un Business Service desde Java Business Host a PEX
Para crear un PEX Business Service, debes implementar `com.intersystems.enslib.pex.BusinessService` en vez de `com.intersystems.gateway.bh.BusinessService`.
El patrón de diseño utilizado por PEX para el Business Service ha cambiado de uno en el que se espera que el servicio inicie un subproceso para producir mensajes, a otro en el que el servicio implementa una función que es llamada periódicamente para producir mensajes.
En JBH, tu código se parecería a esto:
```java
@Override
public boolean OnInit(Production p) throws Exception {
production = p;
if (messageThread == null) {
Messager messager = new Messager();
messageThread = new Thread(messager);
messageThread.start();
}
return true;
}
```
En PEX, solo necesitas implementar tres funciones:
```java
public void OnInit() throws Exception {
// Initialization
return;
}
public Object OnProcessInput(Object messageInput) throws Exception {
// Here is where you call SendMessage() or SendMessageAsync()
return null;
}
public void OnTearDown() throws Exception {
// Shut down
return;
}
```
También deberás cambiar la forma en que se utiliza la configuración, se entregan los mensajes y se hace logging. Hablaremos de eso más adelante.
## Cómo cambiar una Business Operation desde Java Business Host a PEX
Para crear una PEX Business Operation, debes implementar `com.intersystems.enslib.pex.BusinessOperation` en vez de `com.intersystems.gateway.bh.BusinessOperation`.
El patrón de diseño para Business Operations es estructuralmente el mismo entre JBH y PEX, pero han cambiado los parámetros a dos puntos de acceso principales.
### Cambios en OnInit()
En PEX, `OnInit()` no requiere de ningún parámetro.
### Cambios en OnMessage()
En PEX, `OnMessage()` recibe un `Object` genérico en vez del `String` usado en JBH. Esto permite al autor de la producción transmitir cualquier tipo de mensaje que desee.
En JBH, tu aplicación pudo haber tenido este aspecto:
```java
public boolean OnMessage(String message) throws Exception {
// Business logic here
return true;
}
```
En PEX, el parámetro es un Java Objetct genérico, que se debe lanzar de forma adecuada, lo que permite transmitir mensajes más complejos que únicamente cadenas. Este es un ejemplo de cómo extraer una solicitud que es una secuencia de archivos:
```java
public Object OnMessage(Object request) throws Exception {
com.intersystems.jdbc.IRISObject streamContainer = (com.intersystems.jdbc.IRISObject)request;
com.intersystems.jdbc.IRISObject str = (com.intersystems.jdbc.IRISObject)streamContainer.get("Stream");
String originalFilename = (String)streamContainer.get("OriginalFilename");
Long contentSize = (Long)str.get("Size");
String content = (String)str.invoke("Read", contentSize);
// Business logic here
return null;
}
```
También deberás cambiar la forma en que se utiliza la configuración, se entregan los mensajes y se hace logging. Hablaremos de eso más adelante.
## Configuración
Se ha simplificado la declaración de la configuración.
La configuración en JBH se declaraba mediante una cadena `SETTINGS` y se obtenía a través de un código que se parece a algo como esto:
```java
String setting = production.GetSetting("Min");
if (!setting.isEmpty()) {
min = Integer.parseInt(setting);
}
```
En PEX, la configuración son solo campos para miembros públicos. Estos se completan automáticamente cuando la clase crea una instancia.
```java
public int Min = 0;
```
Cualquier campo de un miembro público está disponible para que se establezca en una producción, en la medida en que el campo del miembro sea de un tipo básico de Java (String, int, etc.).
## Mensajes
El envío de mensajes es más potente. En JBH, los mensajes se envían como cadenas. En PEX, los mensajes se envían como objetos- IRISObject, para objetos definidos en ObjectScript, o una subclase de `com.intersystems.enslib.pex.Message`, para clases definidas en Java.
En JBH, tu código se parecería a esto:
```java
production.SendRequest(value.toString());
```
En PEX, sería algo como esto:
```java
MyExampleMessageClass req = new MyExampleMessageClass("message to send");
SendRequestAsync(Target, req);
```
## Logging
Todas las funciones de logging son similares, solo que se nombran de manera diferente.
En PEX, se registraría un mensaje informativo a través de `LOGINFO()`
```java
LOGINFO("Received message");
```
## Object Gateway
JBH necesitaba su propio portal. Con PEX, puedes utilizar un único portal de Java para todas tus necesidades de Java. O puedes utilizar varios portales. Depende de ti. Aquí encontrarás una buena [introducción al portal de Java](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EJVG_intro).
## Conclusión y comentarios
Si aún no has probado PEX, ¿a qué está esperando? PEX permite resolver un conjunto mucho más amplio de problemas empresariales con menos código, además de que ahora también puedes hacer cualquier cosa en .NET.
Si tienes salguna pregunta o problema para migrar tu aplicación de JBH a PEX, puedes contactar conmigo o con el Centro de Soporte Internacional (WRC).
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
Ricardo Paiva · 4 sep, 2020
¡Hola desarrolladores!
La clase %Net.SSH.Session permite conectarse a servidores mediante SSH. Lo más habitual es usarlo con SFTP, especialmente en los adaptadores de FTP entrantes y salientes.
En este artículo se dará un breve ejemplo de cómo conectarse a un servidor SSH usando la clase, se describirá las opciones para autenticar y cómo hacer la depuración cuando surjan problemas.
A continuación un ejemplo de cómo hacer la conexión:
~~~
Set SSH = ##class(%Net.SSH.Session).%New()
Set return=SSH.Connect("ftp.intersystems.com")
~~~
Esto crea una nueva conexión, y luego se conecta al servidor SFTP ftp.intersystems.com en el puerto predeterminado. En este punto, el cliente y el servidor han elegido opciones y algoritmos de cifrado, pero ningún usuario ha iniciado sesión aún.
Una vez conectado, podrá elegir cómo realizar la autenticación. Hay tres métodos principales para elegir:
- AuthenticateWithUsername
- AuthenticateWithKeyPair
- AuthenticateWithKeyboardInteractive
Cada uno de estos es un tipo distinto de autenticación. La siguiente es una breve introducción a cada tipo:
#### AuthenticateWithUsername
Esta usa un nombre de usuario y contraseña.
#### AuthenticateWithKeyPair
Esta usa un par de claves pública y privada. La clave pública se debe haber precargado en el servidor, y debe contar con la clave privada correspondiente. Si la clave privada está cifrada en el disco, debe introduzir una contraseña para descifrarla en la llamada al método. Nota: nunca envíe su clave privada a otra persona.
Las claves públicas deben estar en formato OpenSSH, y las claves privadas deben estar cifradas con PEM. El formato OpenSSH se ve así:
~~~
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCfi2Vq+u0rtt2OC84pyrkq1k7WkrS+s76u3a+2gdD43KQ2Z3vSUUfksymJjp11JBZEpOtBVIAy221UKdc7j7Qk6sUjZaK8LIy+bzDVwMyFWgVvQge7EjdWjrJLBRCDXYML6y1Y25XexThkTWSGyXzGNdr+wfIHYn/mIt0hfvrusauvT/9Wz8K2MGAj4BL7UQZpFJrlXzGmewe6++6cZDQQYi0aztwLK798oc9j0LsccdMpqWrjqoU1uANFhYIuUu/T47TEhT+e6M+KFYK5TR998eJTO25IjdN2Tgw0feXhQFF/nngbol0bA4auSPaZQsgokKK+E+Q/8UtBdetEofuV user@hostname
~~~
Las claves privadas cifradas con PEM tienen un encabezado en la parte superior del archivo que se ve así:
~~~
-----BEGIN RSA PRIVATE KEY-----
~~~
y terminan con:
~~~
-----END RSA PRIVATE KEY-----
~~~
#### AuthenticateWithKeyboardInteractive
Permite realizar una autenticación de desafío y respuesta. Por ejemplo, podría pedir el código de un uso enviado por mensaje de texto o generado por una aplicación autenticadora de Google. Para usar este tipo de autenticación, deberá escribir una función lambda para manejar la solicitudes de comandos enviadas por el servidor.
Puede que vea que algunos servidores usan esto con solo una solicitud de nombre de usuario y contraseña, de una forma que para el usuario se ve idéntica a una autenticación por contraseña. Las marcas de depuración SSH descritas a continuación pueden ayudarle a determinar si eso es lo que está viendo.
Un último comentario sobre la autenticación: Si le interesa usar dos formas de autenticación para una única conexión, asegúrese de usar Ensemble/Cache 2018.1+ o cualquier versión de InterSystems IRIS. Esta versión tiene actualizaciones que permiten el uso de múltiples formatos, tales como par de claves y nombre de usuario.
## Qué hacer cuando algo sale mal...
### Algunos errores comunes que podría encontrarse son:
#### Error al intentar obtener el banner
Esto podría verse así:
~~~
ERROR #7500: SSH Connect Error '-2146430963': SSH Error [8010100D]: Failed getting banner [FFFFFFFF8010100D] at Session.cpp:231,0
~~~
Obtener el banner es lo primero que hace un cliente SSH. Si ve este error, debería verificar que se está conectando al servidor correcto y que este es un servidor SFTP.
Por ejemplo: si el servidor es en realidad un servidor FTPS, verá este error. Los servidores FTPS usan SSL, no SSH, y por lo tanto no funcionan con la clase %Net.SSH.Session. Puede usar la clase %Net.FtpSession class para conectarse a un servidor FTPS.
#### No es posible intercambiar claves de cifrado
Este error podría verse así:
~~~
ERROR #7500: SSH Connect Error '-2146430971': SSH Error [80101005]: Unable to exchange encryption keys [80101005] at Session.cpp:238,0
~~~
Este error generalmente significa que el cliente y el servidor no pudieron negociar algoritmos de MAC o cifrado. Si ve este error, puede que necesite actualizar ya sea el cliente o el servidor para agregar compatibilidad con nuevos algoritmos.
Si está usando una versión de Ensemble/Caché anterior a la 2017.1, le recomiendo actualizar a InterSystems IRIS o probar con 2017.1 o posterior. La biblioteca libssh2 se actualizó en la versión 2017.1 y se agregaron múltiples algoritmos nuevos.
Puede ver más detalles en los registros provistos por las marcas de depuración que describo a continuación.
#### Firma inválida para clave pública suministrada
~~~
Error [80101013]: Invalid signature for supplied public key, or bad username/public key combination [80101013] at Session.cpp:418
~~~
Este error podría ser fácil de malinterpretar. Verá este error si su servidor pidió dos formas de autenticación y usted solo facilitó una. Si ese es el caso, continue y pruebe con la próxima. Es posible que todo se arregle.
#### Error -37
Puede ver mensajes sobre el error -37. Por ejemplo, aquí está en el registro de depuración:
~~~
[libssh2] 0.369332 Failure Event: -37 - Failed getting banner
~~~
Siempre que aparezca el error -37, la operación que fracasó volverá a intentarse. Este error no es lo que causó la falla final. Busque otros mensajes de error.
### Las marcas de depuración de SSH
Se puede habilitar el registro detallado de conexiones SSH para una conexión mediante las marcas de depuración de SSH. Las marcas se habilitan con el método SetTraceMethod. Este es un ejemplo de una conexión que las usa:
~~~
Set SSH = ##class(%Net.SSH.Session).%New()
Do SSH.SetTraceMask(511,"/tmp/ssh.log")
Set Status=SSH.Connect("ftp.intersystems.com")
~~~
El primer argumento de SetTraceMask le indica qué recolectar. Es una representación decimal de bits. 511 solicita todos los bits excepto el 512, y es la configuración usada más comúnmente. Si desea conocer más acerca de cada bit, están enumerados en la documentación de la clase %Net.SSH.Session.
El segundo argumento le indica en qué archivo colocar la información de registro sobre la conexión. En este ejemplo usé el archivo /tmp/ssh.log, pero puede ingresar cualquier ruta absoluta o relativa que quiera usar.
En el ejemplo anterior, solo ejecuté el método Connect. Si su problema está en la autenticación, deberá ejecutar también el método de autenticación correspondiente.
Luego de ejecutar su prueba, podrá buscar información en el archivo de registro. Si no está seguro de cómo interpretar el archivo de registro, el Centro Mundial de Respuesta de Intersystems (WRC) puede ayudar.
Artículo
Ricardo Paiva · 4 nov, 2021
En este artículo describiré los procesos para ejecutar pruebas unitarias mediante ObjectScript Package Manager (consulta ), incluyendo el cálculo de la Cobertura de pruebas (mediante ).
## Pruebas unitarias en ObjectScript
Ya hay mucha documentación sobre cómo escribir pruebas unitarias en ObjectScript, por lo que no repetiré nada de eso. Puedes consultar el Tutorial de Pruebas Unitarias aquí:
La práctica recomendada es incluir las pruebas Unitarias en algún lugar/carpeta separada en la estructura de fuentes, ya sea simplemente "/pruebas" o algo más sofisticado. Dentro de InterSystems, terminamos usando /internal/testing/unit_tests/ como nuestro estándar *de facto*, lo que tiene sentido porque las pruebas son internas/no distribuibles y hay otros tipos de pruebas además de las unitarias, pero esto podría ser un poco complejo para proyectos sencillos de código abierto. Puedes ver esta estructura en algunos de nuestros repositorios de GitHub.
Desde el punto de vista del flujo de trabajo, esto es súper fácil en VSCode: solo hay que crear el directorio y colocar las clases allí. Con enfoques más antiguos centrados en el servidor para el control de la fuente (los utilizados en Studio), tendrás que mapear este paquete de manera apropiada, y el enfoque para eso varía según la extensión del control de la fuente.
Desde la perspectiva de los nombres de clases para las pruebas unitarias, mi preferencia personal (y la práctica recomendada de mi grupo) es:
UnitTest.<package/class being tested>[.<method/feature being tested>]
Por ejemplo, si las pruebas unitarias son para el Método Foo en la clase MyApplication.SomeClass, la clase de la prueba unitaria se llamaría UnitTest.MyApplication.SomeClass.Foo; si las pruebas fueran para la clase en su totalidad, simplemente sería UnitTest.MyApplication.SomeClass.
## Pruebas unitarias en ObjectScript Package Manager
¡Hacer que ObjectScript Package Manager esté informado de tus pruebas unitarias es sencillo! Basta con añadir una línea como la siguiente a module.xml (tomada de , una bifurcación del excelente paquete matemático de @Peter.Steiwer de Open Exchange, el cual utilizo como un simple ejemplo inspirador):
`<Module><br> ...<br> <UnitTest Name="tests" Package="UnitTest.Math" Phase="test"/><br></Module>`
Lo que todo esto significa es:
* Las pruebas unitarias están en el directorio "tests" debajo de la raíz del módulo.
* Las pruebas unitarias están en el paquete "UnitTest.Math". Esto tiene sentido, porque las clases que se están probando están en el paquete "Math".
* Las pruebas unitarias se ejecutan en la fase "test" en el ciclo de vida del paquete. (También hay una fase de "verificación" en la que podrían ejecutarse, pero esa es una historia para otro día).
### Cómo ejecutar pruebas unitarias
Con las pruebas unitarias definidas como se explicó anteriormente, el administrador de paquetes ofrece algunas herramientas realmente útiles para ejecutarlas. Todavía puedes configurar ^UnitTestRoot, como lo harías normalmente con %UnitTest.Manager, pero probablemente encontrarás las siguientes opciones mucho más fáciles, especialmente si estás trabajando en varios proyectos en el mismo entorno.
Puedes probar todos estas opciones clonando el repositorio objectscript-math enumerado anteriormente y luego cargarlo con `zpm "load /path/to/cloned/repo/"`, o en tu propio paquete reemplazando "objectscript-math" con los nombres de tus paquetes (y nombres de prueba).
Para recargar el módulo y luego ejecutar todas las pruebas unitarias:
`zpm "objectscript-math test"`
Para simplemente ejecutar las pruebas unitarias (sin recargar):
`zpm "objectscript-math test -only"`
Para simplemente ejecutar las pruebas unitarias (sin recargar) y proporcionar una salida detallada:
`zpm "objectscript-math test -only -verbose"`
Para ejecutar un conjunto de pruebas en particular (es decir, un directorio de pruebas, en este caso, todas las pruebas en UnitTest/Math/Utils) sin recargar, y proporcionar una salida detallada:
`zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils"`
Para ejecutar un caso particular de prueba (en este caso, UnitTest.Math.Utils.TestValidateRange) sin recargar y proporcionar una salida detallada:
`zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange"`
O, si solo estás resolviendo los problemas de un único método de prueba:
`zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull"`
Cálculo de la Cobertura de pruebas mediante ObjectScript Package Manager
Así que tienes algunas pruebas unitarias, pero ¿son buenas? Calcular la cobertura de pruebas no responderá completamente a esa pregunta, pero al menos ayuda. Presenté esto en la Convención anual (*Global Summit*) de InterSystems, allá por el año 2018 - aquí puedes ver el vídeo: https://youtu.be/nUSeGHwN5pc .
Lo primero que tendrás que hacer es instalar el paquete de cobertura de pruebas:
`zpm "install testcoverage"`
Ten en cuenta que esto no requiere la instalación/ejecución de ObjectScript Package Manager; puedes encontrar más información en Open Exchange:
Dicho esto, puedes aprovechar al máximo la herramienta de cobertura de pruebas si también utilizas ObjectScript Package Manager.
Antes de ejecutar pruebas, debes especificar qué clases/rutinas esperas que cubran tus pruebas. Esto es importante porque, en las bases de código muy grandes (por ejemplo, HealthShare), calcular y recopilar la Cobertura de pruebas para todos los archivos del proyecto puede requerir más memoria de la que tiene tu sistema. (Específicamente, gmheap para un análisis por linea de código, si tienes curiosidad).
La lista de archivos se incluye en un archivo llamado cover.list, que está dentro de la raíz de la prueba unitaria. Diferentes subdirectorios (conjuntos) de pruebas unitarias pueden tener su propia copia de esto para anular las clases/rutinas que se rastrearán mientras se ejecuta el conjunto de pruebas.
Para ver un ejemplo sencillo con objectscript-math, consulta: . La [guía de usuario para la Herramienta de cobertura de pruebas](https://github.com/intersystems/TestCoverage#user-guide) incluye más detalles.
Para ejecutar las pruebas unitarias con el cálculo de la Cobertura de pruebas habilitado, solo hay que añadir un argumento más al comando, especificando que se debe utilizar TestCoverage.Manager en vez de %UnitTest.Manager para ejecutar las pruebas:
`zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager"`
La salida (incluso en el modo resumido) incluirá una URL donde podrás ver qué líneas de tus clases/rutinas estaban cubiertas por las pruebas unitarias, así como algunas estadísticas agregadas.
## Siguientes pasos
¿Qué sucede con la automatización de todo esto en CI? ¿Qué sucede con los reportes de resultados de las pruebas unitarias y las puntuaciones/diffs de cobertura? ¡También puedes hacer eso! Para ver un ejemplo sencillo usando Docker, Travis CI y codecov.io, consulta . Estoy planeando escribir esto en un artículo futuro que analice algunos enfoques diferentes.
Artículo
Alberto Fuentes · 25 mayo, 2021
En este artículo, me gustaría hablar sobre el enfoque *spec-first* para el desarrollo de una API REST.
Mientras que el desarrollo tradicional *code-first* de una API REST es así:
* Escribir el código
* Habilitarlo en REST
* Documentarlo (como una API REST)
*Spec-first* sigue los mismos pasos, pero a la inversa. Comenzamos con una especificación, — que también actúa como documentación — , generamos el código base de la aplicación REST a partir de ella, y finalmente escribimos la lógica de negocio concreta que nos haga falta.
Esto ofrece varias ventajas:
* Siempre se dispone de documentación relevante y útil para desarrolladores externos o de frontend que quieran utilizar tu API REST
* La especificación creada en OAS (Swagger) se puede importar a una variedad de herramientas que permiten la edición, generación de clientes, administración de la API, pruebas unitarias y automatización o simplificación de muchas otras tareas
* Arquitectura de la API mejorada. En el enfoque *code-first*, la API se desarrolla método a método, por lo que un desarrollador puede perder fácilmente la pista de la arquitectura general de la API. Sin embargo, con el enfoque *spec-first*, el desarrollador se ve obligado a interactuar con una API desde la posición de consumidor de la misma, lo que con frecuencia puede ayudarle a diseñar una arquitectura de API más limpia
* Desarrollo más rápido: como todo el código base se genera automáticamente, no tendrás que escribirlo, lo único que necesitas es desarrollar la lógica de negocio de tu aplicación en particular.
* Obtienes sugerencias de forma más rápida: los consumidores pueden obtener una visión de la API inmediatamente y pueden ofrecer sugerencias de forma más sencilla, simplemente modificando la especificación.
¡Vamos a desarrollar nuestra API con un enfoque *spec-first*!
### Plan
1. Desarrollo de la especificación en swagger
* Docker
* Localmente
* Online
2. Carga de la especificación en IRIS
* API management REST API
* ^REST
* Clases
3. ¿Qué pasó con nuestra especificación?
4. Implementación
5. Desarrollos posteriores
6. Consideraciones
* Parámetros especiales
* CORS
7. Carga de la especificación en IAM
### Desarrollo de la especificación
El primer paso es, naturalmente, escribir la especificación. InterSystems IRIS es compatible con la Open API Specification (OAS):
> **OpenAPI Specification** (anteriormente Swagger Specification) es un formato de descripción de las APIs para API REST. Un archivo OpenAPI te permite describir toda tu API, incluyendo:
>
> * Endpoints disponibles (`/users`) y operaciones en cada endpoint (`GET /users`, `POST /users`)
> * Los parámetros de operación entrada y salida, para cada operación
> * Métodos de autenticación
> * Información de contacto, licencia, términos de uso y otro tipo de información
>
> Las especificaciones de la API se pueden escribir en YAML o JSON. El formato es fácil de aprender y leer tanto para los humanos como para las máquinas. La especificación completa de OpenAPI se puede encontrar en GitHub: [Especificación OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
- de la documentación de Swagger.
Utilizaremos Swagger para escribir nuestra API. Hay varias formas de utilizar Swagger:
* [Online](https://editor.swagger.io/)
* Docker: `docker run -d -p 8080:8080 swaggerapi/swagger-editor`
* [Instalación local](https://swagger.io/docs/open-source-tools/swagger-editor/)
Después de instalar/ejecutar Swagger, deberías ver esta ventana en un navegador web:
A la izquierda se edita la especificación de la API; y a la derecha se ve inmediatamente la herramienta de prueba/documentación de la API representada de una forma visual.
Vamos a cargar nuestra primera especificación de la API en él (en [YAML](https://en.wikipedia.org/wiki/YAML)). Se trata de una API sencilla con una solicitud GET que devuelve un número aleatorio en un rango especifico.
Especificación Math API
swagger: "2.0"
info:
description: "Math"
version: "1.0.0"
title: "Math REST API"
host: "localhost:52773"
basePath: "/math"
schemes:
- http
paths:
/random/{min}/{max}:
get:
x-ISC_CORS: true
summary: "Get random integer"
description: "Get random integer between min and max"
operationId: "getRandom"
produces:
- "application/json"
parameters:
- name: "min"
in: "path"
description: "Minimal Integer"
required: true
type: "integer"
format: "int32"
- name: "max"
in: "path"
description: "Maximal Integer"
required: true
type: "integer"
format: "int32"
responses:
200:
description: "OK"
Esto es en lo que consiste.
Información básica sobre nuestra API y la versión de OAS utilizada.
swagger: "2.0"
info:
description: "Math"
version: "1.0.0"
title: "Math REST API"
Host del servidor, protocolo (http, https) y nombres de las aplicaciones web:
host: "localhost:52773"
basePath: "/math"
schemes:
- http
A continuación, especificamos una ruta (por lo que la URL completa sería `http://localhost:52773/math/random/:min/:max`) y el método de solicitud HTTP (get, post, put, delete):
paths:
/random/{min}/{max}:
get:
A continuación, especificamos la información sobre nuestra solicitud:
x-ISC_CORS: true
summary: "Get random integer"
description: "Get random integer between min and max"
operationId: "getRandom"
produces:
- "application/json"
parameters:
- name: "min"
in: "path"
description: "Minimal Integer"
required: true
type: "integer"
format: "int32"
- name: "max"
in: "path"
description: "Maximal Integer"
required: true
type: "integer"
format: "int32"
responses:
200:
description: "OK"
En esta parte definimos nuestra solicitud:
* Habilitar esta ruta para CORS (explicaremos esto más adelante)
* Proporcionar _summary_ y _description_
* _operationId_ permite una referencia dentro de las propias especificaciones, además es un nombre de método generado en nuestra clase de implementation
* _produces_: formato de respuesta (como text, xml, json)
* _parameters_ especifica los parámetros de entrada (ya sea en la URL o en el cuerpo); en nuestro caso especificamos 2 parámetros, el rango para nuestro generador de números aleatorios
* _responses_ lista de respuestas posibles del servidor
Como ves, este formato no es especialmente difícil, aunque hay muchas más funciones disponibles, aquí hay una [especificación](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md).
Por último, vamos a exportar nuestra definición como JSON. Ir a File → Convert y guardar como JSON. La especificación debería tener este aspecto:
Especificación Math API
{
"swagger": "2.0",
"info": {
"description": "Math",
"version": "1.0.0",
"title": "Math REST API"
},
"host": "localhost:52773",
"basePath": "/math",
"schemes": [
"http"
],
"paths": {
"/random/{min}/{max}": {
"get": {
"x-ISC_CORS": true,
"summary": "Get random integer",
"description": "Get random integer between min and max",
"operationId": "getRandom",
"produces": [
"application/json"
],
"parameters": [
{
"name": "min",
"in": "path",
"description": "Minimal Integer",
"required": true,
"type": "integer",
"format": "int32"
},
{
"name": "max",
"in": "path",
"description": "Maximal Integer",
"required": true,
"type": "integer",
"format": "int32"
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
### Carga de la especificación en IRIS
Ahora que tenemos nuestra especificación, podemos generar el código base para esta API REST en InterSystems IRIS.
Para pasar a esta etapa necesitaremos tres cosas:
* Nombre de la aplicación REST: paquete para nuestro código generado (digamos `math`)
* La especificación OAS en formato JSON: la acabamos de crear en un paso anterior
* Nombre de la aplicación WEB: una ruta base para acceder a nuestra API REST (`/math` en nuestro caso)
Hay tres maneras de utilizar nuestra especificación para generar códigos, que son esencialmente lo mismo y solo ofrecen varias maneras de acceder a la misma función
1. Llamar a la rutina `^%REST` (`Do ^%REST` en una sesión terminal interactiva), [documentación](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GREST_routine).
2. Llamar a la clase `%REST` (`Set sc = ##class(%REST.API).CreateApplication(applicationName, spec)`, forma no interactiva), [documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_objectscriptapi).
3. Utilizar API Management REST API, [documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_apimgmnt).
Creo que la documentación describe adecuadamente los pasos necesarios, así que solo tienes que seleccionar uno. Añadiré dos notas:
* En los casos (1) y (2) puedes transmitir a un objeto dinámico, un nombre de archivo o una URL
* En los casos (2) y (3) **debes** realizar una llamada adicional para crear una aplicación WEB: `set sc = #class(%SYS.REST). DeployApplication(restApp, webApp, authenticationType)`, así que en nuestro caso `set sc = ##class(%SYS.REST). DeployApplication("math", "/math")`, obtenemos los valores del argumento `authenticationType` desde el archivo *include* `%sySecurity`, las entradas correspondientes son `$$$Authe*`, por lo que para el acceso no autenticado transmitimos `$$$AutheUnauthenticated`. Si se omite, el parámetro se ajusta de forma predeterminada a la autenticación por contraseña.
### ¿Qué pasó con nuestra especificación?
Si has creado la aplicación con éxito, debería crearse un nuevo paquete `math` con tres clases:
* _Spec_: almacena la especificación tal y como está.
* _Disp_: se llama directamente cuando se invoca el servicio REST. Se ocupa de la gestión del manejo de REST y llama a los métodos de implementación.
* _Impl_: contiene la implementación interna real del servicio REST. Solo deberías editar esta clase.
[Documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_intro#GREST_intro_classes) con más información sobre las clases.
### Implementación
Inicialmente nuestra *implementation class* `math.impl` contiene solo un método, que corresponde a nuestra operación `/random/{min}/{max}`:
/// Get random integer between min and max<br/>
/// The method arguments hold values for:<br/>
/// min, Minimal Integer<br/>
/// max, Maximal Integer<br/>
ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject
{
//(Place business logic here)
//Do ..%SetStatusCode(<HTTP_status_code>)
//Do ..%SetHeader(<name>,<value>)
//Quit (Place response here) ; response may be a string, stream or dynamic object
}
Comenzaremos con la implementación sencilla:
ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject
{
quit {"value":($random(max-min)+min)}
}
Y finalmente podemos llamar a nuestra API REST abriendo esta página en el navegador: `http://localhost:52773/math/random/1/100`
El resultado debería ser:
{
"value": 45
}
También en el editor Swagger al presionar el botón `Try it out`, y rellenando los parámetros de la solicitud, también se enviaría la misma solicitud:
¡Enhorabuena! ¡Nuestra primera API REST creada con un enfoque *spec-first* ya está funcionando!
### Desarrollos posteriores
Por supuesto, nuestra API no es estática y tenemos que agregar nuevas rutas, y así sucesivamente. Con el desarrollo *spec-first*, empiezas por modificar la especificación, después actualizas la aplicación REST (las mismas llamadas que para crear la aplicación) y finalmente escribes el código. Ten en cuenta que las actualizaciones de la especificación son seguras: tu código no se verá afectado, incluso si la ruta se elimina de una especificación, el método no se eliminará en la *implementation class*.
### Consideraciones
¡Más notas!
#### Parámetros especiales
InterSystems añadió parámetros especiales a la especificación de swagger. Son estos:
Nombre
Tipo de datos
Predeterminado
Lugar
Descripción
x-ISC_DispatchParent
classname
%CSP.REST
información
Superclase para la clase dispatch.
x-ISC_CORS
booleano
falso
operación
Marca para indicar que las solicitudes CORS para esta combinación de endpoint/método deben ser soportadas.
x-ISC_RequiredResource
matriz
operación
Lista separada por comas de los recursos definidos y sus modos de acceso (resource:mode) que se requieren para acceder a este endpoint del servicio REST. Por ejemplo: ["%Development:USE"]
x-ISC_ServiceMethod
cadena
operación
Nombre del método de clase llamado en el back end para dar servicio a esta operación; de forma predeterminada es operationId, que normalmente es el más adecuado.
#### CORS
Hay tres maneras de activar el soporte de CORS.
1. En cada ruta, especificando `x-ISC_CORS` como verdadero. Eso es lo que hemos hecho en nuestra API REST Math.
2. En cada API, añadiendo
Parameter HandleCorsRequest = 1;
y recompilando la clase. También sobreviviría a la actualización de las especificaciones.
3. (Recomendada) En cada API, mediante la implementación de la superclase *custom dispatcher* (debe extender `%CSP.REST`), y escribiendo la lógica del procesamiento de CORS allí. Para utilizar esta superclase, añade `x-ISC_DispatchParent` a tu especificación.
### Carga de la especificación en IAM
Por último, vamos a añadir nuestra especificación en IAM para que sea publicada por otros desarrolladores.
Si no has comenzado con IAM, consulta [este artículo](https://es.community.intersystems.com/node/465921). También explica cómo publicar APIs REST por medio de IAM, por eso no lo describimos aquí. Es posible que quieras modificar los parámetros spec `host` y `basepath` para que apunten a IAM, en lugar de a la instancia de InterSystems IRIS.
Abre el portal del Administrador de IAM y ve a la pestaña `Specs` en el espacio de trabajo correspondiente.
Haz clic en el botón `Add Spec` e introduce el nombre de la nueva API (`math` en nuestro caso). Después de crear nuevas especificaciones en IAM, haz clic en `Editar` y pega el código de las especificaciones (JSON o YAML, no importa para IAM):
No olvides hacer clic en `Update file`.
Ahora nuestra API está publicada para los desarrolladores. Abre el *Developer Portal* y haz clic en `Documentation` en la esquina superior derecha. Además de las tres API predeterminadas, nuestra nueva `API REST Math` debería estar disponible:
Ábrela:
¡Ahora los desarrolladores pueden ver la documentación de nuestra nueva API y probarla en el mismo lugar!
###
### Conclusiones
InterSystems IRIS simplifica el proceso de desarrollo de una API REST y el enfoque spec-first permite una gestión más rápida y sencilla del ciclo de vida de la API REST. Con este enfoque, puedes utilizar una variedad de herramientas para una variedad de tareas relacionadas, como la generación de clientes, las pruebas unitarias, la administración de la API y muchas otras más.
### Enlaces
* [Especificación OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
* [Cómo crear servicios REST](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST)
* [Presentación de IAM](https://es.community.intersystems.com/post/presentaci%C3%B3n-de-intersystems-api-manager)
* [Documentación de IAM](https://docs.intersystems.com/irislatest/csp/docbook/apimgr/index.html)
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
Alberto Fuentes · 29 jul, 2019
¡Hola a tod@s!
Hoy os traigo un artículo de Kyle Baxter sobre búsquedas de texto libre que vale la pena guardar como referencia :)
¿Os gustaría buscar de forma eficiente campos de texto libres almacenados en vuestra aplicación? ¿Lo habéis intentado alguna vez pero no habéis encontrado una manera que os ofrezca un buen rendimiento? Hay un truco especial que resuelve el problema :)
Como es habitual, si preferís la versión TL;DR (Demasiado largo, no lo he leído), podéis ir directamente al final del artículo, pero preferiríamos que leyeseis el artículo entero para evitar herir sentimientos.
Vamos a utilizar como ejemplo la clase Sample.Company que encontrareis en:
IRIS - incluida en el paquete de ejemplos https://github.com/intersystems/Samples-Data que puedes descargar e instalar
Caché / Ensemble - namespace SAMPLES
La clase Sample.Company tiene el campo Mission que es un texto generado al azar. Para el ejemplo, se han generado alrededor 256,246 empresas.
/// The company's mission statement.
Property Mission As %String(MAXLEN = 200, POPSPEC = "Mission()");
Supongamos que queremos buscar en este campo de texto:
SELECT * FROM Sample.Company WHERE LIKE '%agile%'
Es una consulta bastante razonable, pero ¿cómo funciona? Al no tener un índice, claramente necesita leer cada entrada, de modo que obtenemos 256,277 referencias a globals de nuestras 7,854 filas devueltas. ¡Esto no está bien! Añadamos un índice a Sample.Company y veamos si podemos hacerlo mejor:
Index MissionIndex on Mission;
Construimos el índice y ejecutamos la misma consulta. ¿Qué obtenemos ahora? 279,088 referencias a globals.
Pero... ¡eso son más referencias a globals! ¿Acaso no es malo? ¡Los índices debían habernos ayudado!
Calmémonos. Además, cuando nos enfrentamos a un comportamiento contradictorio, es mejor tomarse un momento para pensar. ¿Cuál es el coste de leer un índice en comparación con leer la tabla de datos completa? Un índice será más pequeño, así que leer el índice MissionIndex completo será una operación menos costosa que hacer un escaneo completo de la tabla. Pero además del índice, necesitaremos leer algunas partes de la tabla para mostrar los resultados. En este caso, aunque haya más referencias a globals, es menos trabajo (asumiendo que todo está en disco). Por supuesto, podemos hablar mucho más sobre este comportamiento y la estructura de bloques de bases de datos de Caché pero eso está fuera del ámbito de este artículo.
De acuerdo, así que nuestra primera modificación y el ahorro de nuestra búsqueda posiblemente funcionaron, pero ciertamente el resultado fue menor de lo que nos gustaría. Buscamos algo mucho más rápido, queremos menos referencias a globals y que sea sencillo de implementar ¿qué podemos hacer? la respuesta es un índice iFind .
Vamos a definir el índice de la siguiente forma:
Index MissioniFind on (Mission) as %iFind.Index.Basic;
Reconstruimos los índices ##class(Sample.Company).%BuildIndices() y ahora necesitamos decirle a la consulta que utilice este nuevo índice:
SELECT * FROM Sample.Company WHERE %ID %FIND search_index(MissioniFind,’agile’)
Ahora, esta consulta obtiene las mismas 7,854 filas, pero lo hace con 7,928 referencias a globals. ¡Son apenas más referencias a globals que filas! Lo cual es algo muy bueno.
¿Podemos combinar esto con otros índices? Sí, las combinaciones de índices funcionan muy bien con esta tecnología.
¿Existe alguna restricción? Pues sí. Necesita que el ID de la clase sea compatible con bitmaps (mapas de bits). Es decir, el ID de la tabla debe ser un número entero positivo.
¿Dónde puedo obtener más información? En la documentación, desde luego:
https://irisdocs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLSRCH_txtsrch_indexing
https://irisdocs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25iFind.Index.Basic
------------------------------------------------------
TL;DR (Demasiado largo, no lo he leído):
Para buscar en un campo de texto libre:
1. Añadir un índice iFind sobre el campo de texto libre:
Index <IndexName> on <FreeTextField> As %iFind.Index.Basic
2. Reconstruir índices de forma habitual, por ejemplo:
do ##class(Sample.Company).%BuildIndices()
3. Re-escribir la consulta SQL de la siguiente manera en la cláusula WHERE:
…WHERE ID %FIND search_index(<IndexName>,<Search Value>) AND …
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
Alberto Fuentes · 25 ago, 2022
Hace varios años, estaba enseñando los conocimientos básicos de nuestro framework %UnitTest durante la clase de Fundamentos de Caché (ahora llamada Developing Using InterSystems Objects and SQL). Un alumno preguntó si era posible recoger estadísticas de rendimiento mientras se ejecutan pruebas unitarias. Unas semanas más tarde, añadí un código adicional a los ejemplos de %UnitTest para responder a esa pregunta. Ahora lo comparto con la Comunidad.
La clase %SYSTEM,Process ofrece varias métricas que puedes recoger para un proceso (además de Duration).
Duration
Lines Executed
Global References
System CPU Time
User CPU Time
Disk Read Time
Para permitir a cualquier prueba unitaria recoger esas estadísticas, crea una subclase de %UnitTest.TestCase y añade propiedades.
Class Performance.TestCase Extends %UnitTest.TestCase
{
Property Duration As %Time;
Property Lines As %Integer;
Property Globals As %Integer;
Property SystemCPUTime As %Integer;
Property UserCPUTime As %Integer;
Property DiskReadTime As %Integer;
}
Cualquier clase de unit test que crees debería heredar de tu nueva subclase en vez de %UnitTest.TestCase.
En la subclase, usa OnBeforeOneTest() para iniciar la recogida de estadísticas para cada prueba unitaria. Para todo excepto DiskReadTime,el código inicia la propiedad con el valor actual.
/// initialize performance stats
Method OnBeforeOneTest(testname As %String) As %Status
{
// initialize with current values
set ..Duration = $zh
set ..Lines = $system.Process.LinesExecuted()
set ..Globals = $system.Process.GlobalReferences()
set ..SystemCPUTime = $piece(CPUTime, ",", 1)
set ..UserCPUTime = $piece(CPUTime, ",", 2)
// reset disk read time to 0 and start counting
do $system.Process.ResetDiskReadTiming()
do $system.Process.EnableDiskReadTiming()
return $$$OK
}
Usa OnAfterOneTest() para terminar la recogida de estadísticas para cada prueba unitaria. Para todo excepto DiskReadTime, el código elimina el valor inicial desde el valor actual.
/// Finalize performance stats
/// This is where you could also add code to save the counters to a separate table for analysis.
Method OnAfterOneTest(testname As %String) As %Status
{
set ..Duration = $zh - ..Duration
set ..Lines = $system.Process.LinesExecuted() - ..Lines
set ..Globals = $system.Process.GlobalReferences() - ..Globals
set CPUTime = $system.Process.GetCPUTime()
set ..SystemCPUTime = $piece(CPUTime, ",", 1) - ..SystemCPUTime
set ..UserCPUTime = $piece(CPUTime, ",", 2) - ..UserCPUTime
// get disk read time and stop counting
set ..DiskReadTime = $system.Process.DiskReadMilliseconds()
do $system.Process.DisableDiskReadTiming()
// add message to unit test log
set msg = "Performance: " _
"Duration: " _ ..Duration _
", Lines: " _ ..Lines _
", Globals: " _ ..Globals _
", System CPU Time: " _ (..SystemCPUTime / 1000) _
", User CPU Time: " _ (..UserCPUTime / 1000) _
", Disk Read Time: " _ (..DiskReadTime / 1000)
do $$$LogMessage(msg)
return $$$OK
}
Hay otro pequeño truco. Puede que quieras ejecutar tus pruebas unitarias con o sin estadísticas de recogida. Así que, el código en el que estás invocando tus pruebas unitarias debe tomar un argumento (podría ser un %Boolean 1 o 0) y de alguna manera pasarlo. Los métodos que realmente ejecutan las pruebas (como RunTest() u otro de los métodos Run*()) toman un array como el 3er argumento, pasado por referencia. Este es un ejemplo:
// create an array to hold the logging argument (1 or 0) and pass it by reference
set p("logging") = logging
do ##class(%UnitTest.Manager).RunTest(test, qualifiers, .p)
Al valor que pasas en la matriz puede accederse en OnBeforeOneTest() y OnAfterOneTest(). Añade esto como la primera línea en ambos métodos:
if (..Manager.UserFields.GetAt("logging") = 0) { return $$$OK }
¡Y ya está!
Espero vuestras preguntas, comentarios e ideas.
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
Alberto Fuentes · 13 sep, 2021
¡Hola desarrolladores!
A veces necesitamos importar datos CSV de forma programática en InterSystems IRIS desde un fichero o una URL. Y esperamos además que automáticamente se genere una clase con los tipos de datos adecuados y los datos importados.
Echadle un ojo al módulo [csvgen](https://openexchange.intersystems.com/package/csvgen) en Open Exchange que hace exactamente eso que hemos descrito.
Si necesitas importar un fichero CSV en IRIS puedes hacer esto:
```
USER>do ##class(community.csvgen).Generate("/usr/data/titanic.csv",,"Data.Titanic")
Class name: Data.Titanic
Header: PassengerId INTEGER,Survived INTEGER,Pclass INTEGER,Name VARCHAR(250),Sex VARCHAR(250),Age INTEGER,SibSp INTEGER,Parch INTEGER,Ticket VARCHAR(250),Fare MONEY,Cabin VARCHAR(250),Embarked VARCHAR(250)
Records imported: 891
USER>
```
O si los datos CSV están publicados en internet, por ejemplo [Datos COVID-19 en GitHub](https://github.com/CSSEGISandData/COVID-19/blob/master/csse_covid_19_data/csse_covid_19_daily_reports/05-29-2020.csv), puedes hacer esto otro:
```
USER>d ##class(community.csvgen).GenerateFromURL("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/05-29-2020.csv",",","Data.Covid19")
Class name: Data.Covid19
Header: FIPS INTEGER,Admin2 VARCHAR(250),Province_State VARCHAR(250),Country_Region VARCHAR(250),Last_Update DATE,Lat MONEY,Long_ DOUBLE,Confirmed INTEGER,Deaths INTEGER,Recovered INTEGER,Active INTEGER,Combined_Key VARCHAR(250),Incidence_Rate DOUBLE,Case-Fatality_Ratio DOUBLE
Records imported: 3522
USER>
```
## Instalación
Puedes instalar el módulo directamente utilizando ZPM:
```
USER>zpm
zpm:USER>install csvgen
```
El módulo csvgen en realidad es un envoltorio del método [CSV2CLASS](https://cedocs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&CLASSNAME=%25SQL.Util.Procedures) .
Si os animáis a colaborar, ¡cualquier mejora es bienvenida!
Artículo
Muhammad Waseem · 19 nov, 2021
En mis artículos anteriores, mostré los pasos para conectar, recuperar y guardar datos en Caché desde Appeon PowerBuilder usando ODBC.
En este artículo, mostraré cómo eliminar datos de Caché con Appeon PowerBuilder (https://www.appeon.com/products/powerbuilder) usando ODBC.
Estoy usando Company.cls de Samples-Data (https://github.com/intersystems/Samples-Data/tree/master/cls/Sample)
¡Empecemos!
Paso 1: En primer lugar, debemos establecer una conexión (https://community.intersystems.com/post/connecting-cach%C3%A9-appeon-powerbuilder-using-odbc)
Paso 2: Necesitamos crear un objeto de ventana de datos (datawindow object) que se vinculará a la clase Company.
En el menú File, selecciona New y elige el objeto de ventana de datos Freeform en la pestaña DataWindow.
Paso 3: Selecciona SQL Select de la lista de fuentes de datos
Paso 4: Selecciona sample.company de la lista de tablas:
Paso 5: Selecciona las columnas deseadas en la lista de columnas y haz clic en Return.
Paso 6: Esto abrirá una vista de diseño. Guarda la ventana de datos (datawindow) como d_company_entry después de los ajustes deseados.
Paso 7: Asegúrate de anular la selección de la columna id en la lista de columnas sin fecha (Updatable columns) y selecciona id en la lista desplegable del campo Identity Column, ya que la identificación se generará automáticamente desde Caché.
Paso 8: En el control de ventana, añade el objeto de ventana de datos (d_company_entry) que ya creamos al control de ventana de datos
Paso 9: Añade Retrieval Arguments para recuperar los datos basados en ID. Para esto, añadiremos (Desde el menú Design > Retrieval Arguments) en nuestro control de ventana de datos
Paso 10: Añade el parámetro recién añadido en la cláusula where
Paso 11: Recupera los datos basados en el ID utilizando la función de control de la ventana de datos Retrieve (ID)
Paso 12: Elimina el registro usando la función de control de ventana de datos deleterow () . Es lo mismo que el método de clase% DeleteID
Eso es todo. Se elimina el ID 21
Artículo
Muhammad Waseem · 14 jun, 2022
Esta es una comparación creada en Python y Objectscript en InterSystems IRIS.
El objetivo es comparar la velocidad para enviar y recibir mil solicitudes/mensajes desde un BP a un BO en Python y en Objectscript.
Consultar [https://github.com/LucasEnard/benchmark-python-objectscript](https://github.com/LucasEnard/benchmark-python-objectscript) para más información.
**IMPORTANTE** : Aquí están los resultados del tiempo en segundos, para enviar **1000 mensajes** de *ida y vuelta* desde un `bp` a un `bo` usando Python, Graph Objectscript y Objectscript.
Los mensajes de cadena se componen de diez variables de cadena.
Los mensajes de objeto se componen de diez variables de objeto, cada objeto como su propio int, float, str y List(str).
| Cadenas de mensajes| Tiempo (segundos) para 1000 mensajes de ida y vuelta |
|------------------------|------------------|
| Python BP | 1.8 |
| BPL | 1.8 |
| ObjectScript | 1.4 |
| Objetos de mensajes| Tiempo (segundos) para 1000 mensajes de ida y vuelta |
|------------------------|------------------|
| Python BP | 3.2 |
| BPL | 2.1 |
| ObjectScript | 1.8 |
La función en la fila tiene x veces el tiempo de la función en la columna:
| Cadenas de mensajes| Python | BPL | ObjectScript |
|------------------------|------------|------------------------|------------------|
| Python | 1 | 1 | 1.3 |
| BPL | 1 | 1 | 1.3 |
| ObjectScript | 0.76 | 0.76 | 1 |
Por ejemplo, la primera fila nos dice que el tiempo de cadena de Python es 1 vez el tiempo de la función de cadena de gráficos de Objectscript y 1,3 veces el tiempo de la función de cadena de Objectscript. (gracias a la primera tabla podemos verificar nuestros resultados: 1.3 * 1.4 = 1.8 1.3 es la x en la tabla en la última columna de la primera fila, 1.4s es el tiempo para los mensajes de cadena en Objectscript vistos en la primera tabla de esta sección y 1.8s es de hecho el tiempo para los mensajes de cadena en python que podemos encontrar buscando en la primera tabla de esta sección o mediante el cálculo como se mostró antes).
Tenemos la función en la fila que tiene x veces el tiempo de la función en la columna:
| Cadenas de mensajes| Python | BPL | ObjectScript |
|------------------------|------------|------------------------|------------------|
| Python | 1 | 1.5 | 1.8 |
| BPL | 0.66 | 1 | 1.2 |
| ObjectScript | 0.55 | 0.83 | 1 |
Artículo
Jose-Tomas Salvador · 31 mayo, 2023
Este es un artículo de la página de "Preguntas frecuentes" (FAQ) de InterSystems.
1. Exportar API
a. Usa $system.OBJ.Export() para especificar rutinas individuales para exportar. Por ejemplo:
do $system.OBJ.Export("TEST1.mac,TEST2.mac","c:\temp\routines.xml",,.errors)
El formato que debes especificar es: NombreDeLaRutina.extension, y la extensión puede ser: mac, bas, int, inc, obj.
Los errores durante la exportación se almacenan en la variable "errors".
Echa un vistazo a la referencia de clase %SYSTEM.OBJ para más detalles sobre $system.OBJ.Export().
b. Usa $system.OBJ.Export() incluso al hacer una exportación genérica usando * (wildcards). Por ejemplo:
do $system.OBJ.Export("*.mac",c:\temp\allmacroutines.xml")
*Antes de la versión 2008.1, utiliza $system.OBJ.ExportPattern().
2. Importar API
a. Usa $system.OBJ.Load() para importar todas las rutinas contenidas en el fichero. Por ejemplo:
do $system.OBJ.Load("c:\temp\routines.xml",,.errors)
b. Importa solo algunas de las rutinas contenidas en el fichero
Observa el ejemplo de abajo. Si quieres seleccionar e importar solo algunas de las rutinas incluidas en el fichero XML, pon a 1 el 5º argumento "listonly" en una primera ejecución, y carga el archivo XML con $system.OBJ.Load(), estableciendo el 4º argumento (argumento de salida, list en el ejemplo abajo). Esto creará una lista de elementos en la variable list. Después podremos recorrer esa lista y decidir que elementos (loaditem) queremos cargar, volviendo a ejecutar $system.OBJ.Load() e indicando el elemento a cargar en el 6º argumento. Lo puedes ver más claramente en este ejemplo:
Set file="c:\temp\routines.xml" // First get the list of items contained in the XML Do $system.OBJ.Load(file,,.errors,.list,1 /* listonly */) Set item=$Order(list("")) Kill loaditem While item'="" { If item["Sample" Set loaditem(item)="" { // Import only those containing Sample Set item=$Order(list(item)) } } // Execute import process with created list Do $system.OBJ.Load(file,,.errors,,,.loaditem)
Artículo
Ricardo Paiva · 19 feb, 2021
Si está buscando una forma ingeniosa para integrar su solución de IRIS en el ecosistema de Amazon Web Services, en una aplicación sin servidor o en `Boto3` (un potente script de Python), usar la [API nativa de IRIS para Python](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_PYNATIVE) podría ser el camino a seguir. No es necesario que invierta demasiado tiempo en la implementación de una producción hasta que deba acercarse y obtener algo o establecer algo en IRIS para hacer que su aplicación ejecute su característica más sobresaliente, así que esperamos este artículo sea útil y desarrolle algo aunque solamente usted pueda usarlo, ya que eso también es importante.

Si necesita algunas excusas para implementarla, utilice las siguientes:
* Debe cargar el Activador que generará previamente los tokens en Cognito para buscar y completar el contexto de la identificación del paciente en el token. Esto con la finalidad de utilizar una solución basada en SMART on FHIR(R), la cual implementará un flujo de trabajo en OAUTH2.
* Quiere publicar el suministro de la configuración para los parámetros en IRIS que se basa en el tipo de instancia, grupo de nodo, Toaster o el clúster ECS que inició para ejecutar IRIS en el anillo cero.
* Quiere impresionar a su familia y amigos en Zoom con sus habilidades para administrar IRIS sin que su shell tenga que abandonar la CLI de AWS.
## Lo más importante
Aquí proporcionamos una función lambda de AWS que se comunica con IRIS y brindaremos algunos ejemplos sobre cómo utilizarla, además indicaremos cómo podemos interactuar con ella en varias funciones con la esperanza de que todos podamos discutir sobre ella y **publicarla en pip para facilitar las cosas.**
## ¿¿¿¿Tiene problemas????

Consulte el Stream
## En todos los casos...
Para participar en la diversión, necesitará eliminar algunas cosas de sus planes.
### Interconexión
Tiene a IRIS ejecutándose en cualquier lugar que desee, funcionando en una AWS VPC con exactamente dos subredes y un grupo de seguridad que permite el acceso al super servidor que inicia IRIS... utilizamos 1972 por razones nostálgicas y por el simple hecho de que InterSystems se tomó el tiempo para registrar ese puerto con [IANA](https://tools.ietf.org/html/rfc6335), y si agrega el nuevo puerto en `/etc/services` sin registrarlo, sufrirá las mismas consecuencias que si arrancara la etiqueta de un colchón nuevo. En nuestro caso, es un conjunto de réplicas de las instancias EC2 con las comprobaciones de estado adecuadas en torno a un balanceador de carga para la red AWS v2.

### Ejemplo de una clase importada
De alguna forma consiguió crear e importar la clase a en la raíz de este repositorio con el namespace `%SYS` en su instancia de IRIS. A continuación, se muestra el ejemplo de una clase que impulsa la salida anterior. Si se pregunta por qué necesitamos una clase para importar aquí, consulte la siguiente nota donde el enfoque recomendado es proporcionar algunas clases envolventes para utilizarlas con Python.
> Nota de los documentos: Aunque estos métodos también pueden utilizarse con las clases que se definieron en la Biblioteca de clases de InterSystems, una de las prácticas recomendadas consiste en llamarlos indirectamente, desde el interior de una clase o rutina definida por el usuario. Muchos métodos de clase devuelven solo un código de estado, y transmiten los resultados reales a un argumento (al que no es posible acceder por la API nativa). Las funciones definidas por el sistema (incluidas como parte de las Funciones de ObjectScript en la Referencia de ObjectScript) no pueden llamarse directamente.
Ejemplo de la clase:
```
Class ZDEMO.IRIS.Lambda.Operations Extends %Persistent
{
ClassMethod Version() As %String
{
Set tSC = 0
Set tVersion = $ZV
if ( tVersion '="" ) { set tSC = $$$OK }
Set jsonret = {}
Set jsonret.status = tSC
Set jsonret.payload = tVersion
Quit jsonret.%ToJSON()
}
}
```
Tenga en cuenta que decidí trabajar según lo establecido en los métodos, de modo que siempre emite un objeto JSON como respuesta, lo cual también me permite enviar el estado y posiblemente subsanar las deficiencias que implica devolver algunas cosas como referencia.
### AWS Access
Obtenga algunas claves de acceso IAM que le permitirán suministrar e invocar la función Lambda con la que cambiaremos el mundo.
Comprobación previa:
```
IRIS [ $$$OK ]
VPC [ $$$OK ]
Subnets [ $$$OK ]
Security Group [ $$$OK ]
IAM Access [ $$$OK ]
Imported Class [ $$$OK ]
```
$$$OK, **Lesgo**.
## Empaquetar la API nativa de IRIS para Python con el fin de utilizarla en la función Lambda
Esta parte sería fantástica si fuera un paquete pip, especialmente si solo es para Linux, ya que las funciones Lambda de AWS se ejecutan en Linux Boxen. Cuando realizamos esta asignación la API no estaba disponible por medio de pip, pero somos hábiles y podemos lanzar nuestro propio paquete.
```
mkdir iris_native_lambda
cd iris_native_lambda
wget https://github.com/intersystems/quickstarts-python/raw/master/Solutions/nativeAPI_wheel/irisnative-1.0.0-cp34-abi3-linux_x86_64.whl
unzip nativeAPI_wheel/irisnative-1.0.0-cp34-abi3-linux_x86_64.whl
```
A continuación, cree `connection.config`
Por ejemplo: [connection.config](https://raw.githubusercontent.com/basenube/iris_native_lambda/main/examples/connection.config)
Cree su controlador, `index.py` o utilice el que se encuentra en la carpeta de ejemplos, en el [repositorio con demostraciones de GitHub](https://github.com/basenube/iris_native_lambda). Tenga en cuenta que la versión de demostración utiliza tanto variables de entorno como un archivo externo para guardar la información de conectividad de IRIS.
Por ejemplo:
[index.py](https://raw.githubusercontent.com/basenube/iris_native_lambda/main/examples/index.py)
Ahora comprímalo para utilizarlo:
```
zip -r9 ../iris_native_lambda.zip *
```
Cree un bucket S3, y cargue la función zip en él.
```
cd ..
aws s3 mb s3://iris-native-bucket
s3 sync iris_native_lambda.zip s3://iris-native-bucket
```
> Con esto concluye el empaquetado de la API y el controlador para utilizarlos como una función Lambda de AWS.
Ahora, haga clic hasta que la consola termine para crear la función, o utilice algo como Cloudformation para realizar el trabajo:
```
IRISAPIFunction:
Type: "AWS::Lambda::Function"
DependsOn:
- IRISSG
- VPC
Properties:
Environment:
Variables:
IRISHOST: "172.31.0.10"
IRISPORT: "1972"
NAMESPACE: "%SYS"
USERNAME: "intersystems"
PASSWORD: "lovetheyneighbor"
Code:
S3Bucket: iris-native-bucket
S3Key: iris_native_lambda.zip
Description: "IRIS Native Python API Function"
FunctionName: iris-native-lambda
Handler: "index.lambda_handler"
MemorySize: 128
Role: "arn:aws:iam::8675309:role/BeKindtoOneAnother"
Runtime: "python3.7"
Timeout: 30
VpcConfig:
SubnetIds:
- !GetAtt
- SubnetPrivate1
- Outputs.SubnetId
- !GetAtt
- SubnetPrivate2
- Outputs.SubnetId
SecurityGroupIds:
- !Ref IRISSG
```
Eso fue MUCHO, pero ahora puede volverse loco, llamar a IRIS mediante la función lambda con Python y cambiar el mundo.
## ¡Vamos a iniciarlo!
De la forma en que se implementó lo anterior, se espera que la función sea aprobada en un evento del objeto que esté menos estructurado para reutilizarlo, puede ver la idea en el ejemplo para el evento del objeto que se muestra a continuación:
```
{
"method": "Version",
# important, if method requires no args, enforce "none"
"args": "none"
# example method with args, comma seperated
# "args": "thing1, thing2"
}
```
ahora puede hacerlo, si tiene cierta tolerancia para los ejemplos de la línea de comandos, eche un vistazo en la ejecución que se muestra a continuación utilizando la CLI de AWS:
```
(base) sween @ basenube-pop-os ~/Desktop/BASENUBE
└─ $ ▶ aws lambda invoke --function-name iris-native-lambda --payload '{"method":"Version","args":"none"}' --invocation-type RequestResponse --cli-binary-format raw-in-base64-out --region us-east-2 --profile default /dev/stdout
{{\"status\":1,\"payload\":\"IRIS for UNIX (Red Hat Enterprise Linux for x86-64) 2020.2 (Build 210U) Thu Jun 4 2020 15:48:46 EDT\"}"
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
```
Ahora bien, si vamos un poco más lejos, la CLI de AWS admite alias, así que cree uno y podrá jugar integrando completamente su increíble comando con AWS. Este es un ejemplo del alias para la CLI:
```
└─ $ ▶ cat ~/.aws/cli/alias
[toplevel]
whoami = sts get-caller-identity
iris =
!f() {
aws lambda invoke \
--function-name iris-native-lambda \
--payload \
"{\"method\":\""${1}"\",\"args\":\"none\"}" \
--invocation-type RequestResponse \
--log-type None \
--cli-binary-format raw-in-base64-out \
gar.json > /dev/null
cat gar.json
echo
echo
}; f
```
....y ahora puede hacerlo...

¡Manténgase a salvo!~ ¡Todos los argumentos técnicos son bienvenidos!
Artículo
Luis Angel Pérez Ramos · 25 abr, 2023
Una necesidad habitual en nuestros clientes es la configuración tanto de HealthShare HealthConnect como de IRIS en modo de alta disponibilidad.
Es común en otros motores de integración del mercado que se promocionen con configuraciones de "alta disponibilidad", pero realmente no suele ser del todo cierto. Por lo general dichas soluciones trabajan con bases de datos externas y por lo tanto, si estas no están a su vez configuradas en alta disponibilidad, al producirse una caída de la base de datos o la pérdida de conexión a la misma toda la herramienta de integración queda inutilizable.
En el caso de las soluciones de InterSystems este problema no existe, al ser la base de datos parte y nucleo de las propias herramientas. ¿Y cómo ha solucionado InterSystems el problema de la alta disponibilidad? ¿Con abstrusas configuraciones que podrían arrastrarnos a una espiral de enajenamiento y locura? ¡NO! Desde InterSystems hemos escuchado y atendido vuestras quejas (como siempre intentamos hacer ;) ) y hemos puesto a disposición de todos nuestros usuarios y desarrolladores la función de mirroring.
Mirroring
¿Cómo funciona el Mirror? El concepto en si es muy sencillo. Como ya sabréis tanto IRIS como HealthShare trabajan con un sistema de journaling que registra toda operación de actualización sobre las bases de datos de cada instancia. Este sistema de journaling es el que posteriormente nos sirve para recuperar las instancias sin apenas pérdida de datos en caso de caída de las mismas. Pues bien, son estos archivos de journal los que se envían entre las instancias configuradas en mirror y permiten mantener permanentemente actualizados las instancias configuradas en mirror.
Arquitectura
Expliquemos brevemente como sería la arquitectura de un sistema configurado en Mirror:
Dos instancias configuradas en modo Failover:
Nodo activo: recibe todas las operaciones de lectura/escritura habituales.
Nodo pasivo: en modo lectura, recibe de forma síncrona cualquier cambio producido en el nodo activo.
0-14 instancias asíncronas: tantas instancias asíncronas como se quieran utilizar, pueden ser de dos tipos:
DR async (Disaster Recovery): nodos en modo lectura que no forman parte del Failover aunque se le puede promocionar manualmente.De ser así podría llegar a promocionarse automáticamente a nodo primario en caso de caída de los otros dos nodos del Failover. La actualización de sus datos es en modo asíncrono, por lo que no se garantiza la frescura de los mismos.
Reporting Asyncs: nodos actualizados de forma asíncrona para su uso en tareas de BI o explotaciones de datos. No pueden ser promocionados al Failover ya que se pueden realizar escrituras sobre los datos.
ISCAgent: instalado en cada servidor donde se encuentre una instancia. Será el encargado de monitorizar el estado de las instancias de dicho servidor. Es otra vía de comunicación entre los servidores del Mirror además de la comunicación directa.
Arbiter: es un ISCAgent instalado de forma independiente a los servidores que forman el Mirror y permite aumentar la seguridad y el control de los failover dentro del mismo monitorizando tanto a los ISCAgent instalados como a las instancias de IRIS/HealthShare. Su instalación no es obligatoria.
Este sería el funcionamiento de un Mirror formado por un failover con dos únicos nodos:
Aviso previo
El proyecto asociado a este artículo no dispone de una licencia activa que permita la configuración del mirror. Si queréis probarlo enviadme directamente un email o añadid un comentario al final del artículo y me pondré en contacto con vosotros.
Despliegue en Docker
Para este artículo vamos primeramente a montar un pequeño proyecto en Docker que nos permita montar 2 instancias en failover con un Arbiter. Por defecto las imágenes de IRIS disponibles para Docker tienen ya instalado y configurado el ISCAgent, por lo que nos podremos saltar ese paso. Será necesario configurar el proyecto que viene asociado al artículo desde un Visual Studio Code, ya que nos permitirá posteriormente trabajar de una forma más cómoda con los archivos del servidor.
Veamos que forma tendría nuestro docker-compose.yml:
version: '3.3'
services:
arbiter:
container_name: arbiter
hostname: arbiter
image: containers.intersystems.com/intersystems/arbiter:2022.1.0.209.0
init: true
command:
- /usr/local/etc/irissys/startISCAgent.sh 2188
mirrorA:
image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0
container_name: mirrorA
depends_on:
- arbiter
ports:
- "52775:52773"
volumes:
- ./sharedA:/shared
- ./install:/install
- ./management:/management
command:
--check-caps false
--key /install/iris.key
-a /install/installer.sh
environment:
- ISC_DATA_DIRECTORY=/shared/durable
hostname: mirrorA
mirrorB:
image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0
container_name: mirrorB
depends_on:
- arbiter
- mirrorA
ports:
- "52776:52773"
volumes:
- ./sharedB:/shared
- ./install:/install
- ./management:/management
command:
--check-caps false
--key /install/iris.key
-a /install/installer.sh
environment:
- ISC_DATA_DIRECTORY=/shared/durable
hostname: mirrorB
Podemos ver que tenemos definidos 3 containers:
Arbiter: correspondiente al ISCAgent (aunque la imagen se llame Arbiter) que se desplegará para el control de las instancias de IRIS que formarán el Failover del Mirror. Al arrancar el container ejecutará un fichero shell que arrancará el ISCAgent escuchando en el puerto 2188 del container.
mirrorA: container en el que se desplegará la imagen de IRIS v.2022.1.0.209 y que posteriormente configuraremos como nodo primario del Failover.
mirrorB: container en el que se desplegará la imagen de IRIS v.2022.1.0.209 y que posteriormente configuraremos como nodo secundario del Failover.
Cuando ejecutemos el comando docker-compose up -d se desplegarán en nuestro Docker los contenedores definidos, debiendose ver tal que así en nuestro Docker Desktop (si lo hacemos desde Windows).
Configuración del mirror.
Con nuestros contenedores desplegados procederemos a acceder a las instancias que vamos a configurar en mirror, la primera se encontrará escuchando en el puerto 52775 (mirrorA) y la segunda en el 52776 (mirrorB). El usuario y la contraseña de acceso serán superuser / SYS
Debido a la que las instancias se encuentran desplegadas en Docker, tendremos dos opciones de para configurar las IP de nuestros servidores. La primera es usar directamente el nombre de nuestros contenedores en la configuración (que es la más sencilla) o bien comprobar las IP que Docker ha asignado a cada contenedor (abriendo la consola y ejecutando un ifconfig que nos devuelva la IP asignada). Por motivos de claridad utilizaremos para el ejemplo los nombres que les hemos dado a cada contenedor como dirección de cada uno dentro de Docker.
Primeramente configuraremos la instancia que utilizaremos como nodo activo del FailOver. En nuestro caso será la que hemos denominado mirrorA.
El primer paso será habilitar el servicio de mirroring, por lo que accederemos al menú de mirror desde el portál de gestión: System Adminsitration --> Configuration --> Mirror Settings --> Enable Mirror Service y marcaremos el check de Service Enabled:
Con el servicio habilitado ya podremos empezar a configurar nuestro nodo activo. Tras habilitar el servicio podréis observar que se han habilitado nuevas opciones en el menú de Mirror:
En este caso, al no tener ninguna configuración de mirror ya creada deberemos crear una nueva con la opción de Create Mirror. Cuando accedemos a dicha opción el portal de gestión nos abrirá una nueva ventana desde la que podremos configurar nuestro mirror:
Veamos con más detalle cada una de las opciones:
Mirror Name: el nombre con el que identificaremos a nuestro mirror. Para nuestro ejemplo lo llamaremos MIRRORSET
Require SSL/TLS: para nuestro ejemplo no configuraremos conexión mediante SSL/TLS aunque en entornos productivos sería más que conveniente para evitar que el archivo de journal se comparta sin ningún tipo de cifrado entre las instancias. Si tenéis interés en configurarlo tenéis toda la información necesaria en la siguiente URL de la documentación.
Use Arbiter: esta opción no es obligatoria, pero es bastante recomendable, ya que añade una capa de seguridad a la configuración de nuestro mirror. Para nuestro ejemplo lo dejaremos marcado e indicaremos la IP en el que tenemos funcionando nuestro Arbiter. Para nuestro ejemplo la IP será en nombre de contenedor "arbiter".
User Virtual IP: en entornos Linux/Unix está opción es muy intersante ya que nos permite configurar una IP virtual para nuestro nodo activo que será gestionada por nuestro Mirror. Esta IP virtual debe pertenecer a la misma subred en la que se encuentren los nodos del Failover. El funcionamiento de la IP virtual es muy sencillo, en caso de caída del nodo activo el mirror configurará automáticamente la IP virtual en el servidor en el que se encuentre el nodo pasivo que va a ser promocionado. De tal forma, la promoción del nodo pasivo a activo será totalmente transparente para los usuarios, ya que estos seguirán conectados a la misma IP, aunque esta se encontrará configurada en un servidor distinto. Si queréis saber más al respecto de la IP virtual podéis revisar esta URL de la documentación.
El resto de la configuración podemos dejarla tal como está. En el lado derecho de la pantalla veremos la información relativa a este nodo en el mirror:
Mirror Member Name: nombre de este miembro del mirror, por defecto tomará el nombre del servidor junto con el de la instancia.
Superserver Address: dirección IP del superserver de este nodo, en nuestro caso, mirrorA.
Agent Port: puerto en el que se ha configurado el ISCAgent correspondiente a este nodo. Por defecto el 2188.
Una vez configurado los campos necesarios podemos proceder a guardar el mirror. Podemos comprobar como ha quedado la configuración desde el monitor del mirror (System Operation --> Mirror Monitor).
Perfecto, aquí tenemos nuestro mirror recién configurado. Como véis únicamente figura el nodo activo que acabamos de crear. Muy bien, vayamos entonces a añadir nuestro nodo pasivo en el Failover. Accedemos al portal de gestión de mirrorB y accedemos al menú de Mirror Settings. Como ya hicimos para la instancia de mirrorA deberemos habilitar el servicio de Mirror. Repetimos la operación y en cuanto se actualicen las opciones del menú elegiremos Join as Failover.
Aquí tenemos la pantalla de conexión al mirror. Expliquemos brevemente que son cada uno de los campos:
Mirror Name: nombre que dimos al mirror en el momento de la creación, en nuestro ejemplo MIRRORSET.
Agent Address on Other System: IP del servidor en el que se encuentra desplegado el ISCAgent del nodo activo, para nosotros será mirrorA
Agent Port: puerto de escucha del ISCAgent del servidor en el que creamos el mirror. Por defecto el 2188.
InterSystems IRIS Instance Name: el nombre de la instancia de IRIS en el nodo activo. En este caso coincide con el del nodo pasivo, IRIS.
Tras grabar los datos del mirror tendremos la opción de definir la información relativa al nodo pasivo que estamos configurando. Echemos nuevamente un vistazo a los campos que podemos configurar del nodo pasivo:
Mirror Member Name: nombre que tomará el nodo pasivo en el mirror. Por defecto formado por el nombre del servidor y la instancia.
Superserver Address: dirección IP de acceso al superserver nuestro nodo pasivo. En este caso mirrorB.
Agent Port: puerto de escucha del ISCAgent instalado en el servidor del nodo pasivo que estamos configurando. Por defecto el 2188.
SSL/TLS Requirement: al no configurarlo en la declaración del mirror no es configurable.
Mirror Private Address: dirección IP del nodo pasivo. Como hemos visto, al usar Docker podremos usar el nombre del contenedor mirrorB.
Agent Address: dirección IP al servidor donde está instalado el ISCAgent. Igual que antes, mirrorB.
Grabamos la configuración tal y como hemos indicado y volvemos al monitor del mirror para comprobar que tenemos todo correctamente configurado. Podemos visualizar el monitor tanto del nodo activo en mirrorA como el del pasivo en mirrorB. Veamos las sutiles diferencias entre ambos.
Monitor del mirror en nodo activo mirrorA:
Monitor del mirror en nodo pasivo mirrorB:
Como véis la información mostrada el similar, cambiando básicamente el orden de los miembros del failover. También las opciones son distintas, veamos alguna de ellas:
Nodo activo mirrorA:
Set No Failover: impide la ejecución del failover en caso de parada de alguna de las instancias que forman parte de él.
Demote other member: elimina al otro miembro del failover (en este caso mirrorB) de la configuración del mirror.
Nodo pasivo mirrorB:
Stop Mirror On This Member: detiene la sincronización del mirror en el nodo pasivo del failover.
Demote To DR Member: "degrada" a este nodo pasando de formar parte del failover con su sincronización en tiempo real al modo Disaster Recovery en modo asíncrono.
Perfecto, ya tenemos nuestros nodos configurados, ahora nos queda el paso final en nuestra configuración. Decidir que tablas pasaran a formar parte del mirror y configurarlo en ambos nodos. Si observáis el README.md del proyecto asociado a este artículo veréis que indicamos la existencia de dos aplicaciones que usamos habitualmente para las formaciones y que son automáticamente desplegadas al arrancar los contenedores de Docker.
La ventaja de usar esta configuración es que no tendremos que replicar a mano los NAMESPACES ni las bases de datos relacionadas de cara a configurar a mano el mirror, ya tenemos en ambas instancias los mismos namespaces y bases de datos.
La primera es COMPANY que nos permite guardar registros de empresas y la segunda es PHONEBOOK que nos permite añadir contactos de personal relacionados con las empresas registradas, así como clientes.
Añadamos una compañía:
Y a continuación un contacto de dicha compañía:
Los datos de la compañía se registrarán en la base de datos COMPANY y los del contacto en PERSONAL, ambas bases de datos están mapeadas para que puedan ser accesibles desde el Namespace PHONEBOOK. Si comprobamos las tablas en ambos nodos veremos que en mirrorA tenemos los datos tanto de la compañía como del contacto pero que el mirrorB aún no hay nada, como es lógico.
Compañías registradas en mirrorA:
Muy bien, procedamos a configurar las bases de datos en nuestro mirror. Para ello, desde nuestro nodo activo (mirrorA), accedemos a la pantalla de administración de las bases de datos locales (System Administrator --> Configuration --> System Configuration --> Local Databases) y pulsamos en la opción de Add to Mirror, seleccionamos de la lista las bases de datos que queremos añadir leyendo con detenimiento el mensaje que se nos muestra en pantalla:
Una vez que hayamos añadido las bases de datos al mirror desde el nodo activo deberemos realizar un backup de las mismas o bien copiar los archivos de base de datos (IRIS.dat) y restaurarlos sobre el nodo pasivo. Si decidís realizar la copia directa de los archivos IRIS.dat tened en cuenta que debería realizarse previamente una pausa de la escritura en el fichero, podéis ver los comandos necesarios en la siguiente URL de la documentación. En nuestro ejemplo no será necesario realizar dicha pausa ya que nadie más que nosotros estamos escribiendo en ella.
Antes de realizar dicha copia de los archivos de las bases de datos comprobemos el estado del mirror desde el monitor del nodo activo:
Veamos ahora el nodo pasivo:
Como podemos observar, desde el nodo pasivo se nos está informando que si bien tenemos configuradas 3 bases de datos en el mirror la configuración aún no está hecha. Procedamos a copiar las bases de datos del nodo activo al pasivo, no olvidemos que debemos desmontar las bases de datos del nodo pasivo para poder hacer la copia y para ello accederemos desde el portal de gestión a System Configuration --> Databases y accediendo a cada una de ellas procedemos a desmontarlas.
¡Perfecto! Bases de datos desmontadas. Accedamos al código del proyecto asociado al artículo desde Visual Studio Code y vemos que tenemos una serie de carpetas donde se encuentran las instalaciones de IRIS, sharedA para el mirrorA y sharedB para el mirrorB. Accedamos a las carpetas donde se encuentran las base de datos COMPANY, CUSTOMER y PERSONAL en (/sharedA/durable/mgr) y procedamos a copiar los IRIS.dat de cada base de datos en el mirror a los directorios oportunos del mirrorB (/sharedB/durable/mgr).
Una vez finalizada la copia montamos nuevamente las bases de datos de mirrorB y comprobamos desde el monitor del mirror en mirrorB el estado de las bases de datos configuradas:
¡Bingo! nuestro mirror ha reconocido las bases de datos y ahora sólo necesitaremos activarlas y ponerlas al día. Para ello pulsaremos sobre la acción Activate y a continuación sobre Catchup, que aprecerá tras la activación. Veamos como quedan finalmente:
Perfecto, nuestras bases de datos ya están correctamente configuradas en mirror, si consultamos la base de datos de COMPANY deberíamos ver la que registramos inicialmente desde mirrorA:
Obviamente nuestra base de datos COMPANY tiene el registro que introdujimos con anterioridad en mirrorA, al fin y al cabo hemos copiado toda la base de datos. Procedamos a añadir una nueva empresa desde mirrorA a la que llamaremos "Another company" y procedamos a consultar nuevamente la tabla de la base de datos COMPANY:
Aquí la tenemos. Únicamente nos quedará asegurarnos de que nuestras bases de datos configuradas en mirror se encuentran únicamente en modo lectura en nuestro nodo pasivo mirrorB:
¡Ahí están! en modo R de lectura. Pues ya tenemos nuestro mirror configurado y nuestras bases de datos sincronizadas. En el caso de que tuviesemos funcionando producciones no sería ningún problema ya que el mirror se encarga de forma automática de gestionarlas, arrancándolas en el nodo pasivo caso de caída del nodo activo.
¡Muchas gracias todos los que habéis llegado hasta este punto! Ha sido largo pero confío que os resulte de utilidad.
Artículo
Mario Sanchez Macias · 13 sep, 2022
Nota: Lo que sigue es solo una guía. Cada cliente es diferente.
A través de nuestra experiencia en soporte ayudando a clientes, hemos visto muchos casos en los que no tener un plan de actualización adecuado (y documentado) conduce a problemas inesperados con prioridad de Crisis. En algunos casos, podemos solucionar el problema durante el periodo de actualización, pero no siempre, ya que algunas situaciones pueden requerir una investigación más exhaustiva que puede llevar días o incluso meses.
Es esencial documentar el proceso de actualización, incluyendo los pasos que se deben efectuar antes, durante y después de la actualización, ¡incluso en servidores y aplicaciones pequeñas! Además, un plan de actualización documentado es muy útil cuando involucra a terceros, como proveedores de software o hardware. Al entregar este documento a un proveedor externo (como InterSystems) se acelerará la comprensión de todo el contexto.
Después de trabajar con diferentes documentos, me gustaría compartir unas instrucciones generales para ayudaros a crear un plan de actualización o para ponerlo al día. Por supuesto, se me escaparán algunas cosas. Estoy seguro de que algunos de vosotros tenéis mucha experiencia y podréis añadir ideas y sugerencias, ¡así que no dudéis en comentar este artículo!
Nota: Lo que sigue es solo una guía. Cada cliente es diferente.
Manual para realizar actualizaciones con éxito
Cada actualización a una versión principal (major) debería tener unos pasos y estrategias imprescindibles, similares a las que se describen aquí. Nuestra experiencia con clientes demuestra que un plan detallado es clave para una actualización con éxito, con pruebas y acciones documentadas.
Introducción
Incluye una breve descripción de la actualización, sin detalles. Incluye el objetivo y la situación actual.
1. Infraestructura
Incluye una tabla con los datos esenciales y documentos detallados (listos para ser enviados). Debe incluir la arquitectura actual y la futura (si corresponde).
Arquitectura ACTUAL (ejemplo general)
Servidor
IP
FUNCIÓN
HW
Producto
Sistema Operativo
PROD1
192.178.1.10
REPLICAR
10 cores 20 RAM
Caché 2017.2
Windows 2012
PROD2
192.178.1.11
REPLICAR
10 cores 20 RAM
Caché 2017.2
Windows 2012
192.178.1.30
VIP
ARB
192.178.1.12
Árbitro
2 cores 4 RAM
Caché 2017.2
Windows 2012
BCK
192.178.1.13
Recuperación en caso de desastre (DR)
24cores 10 RAM
Caché 2017.2
Windows 2012
DetallesExtrae los siguientes datos y guárdalos en un sitio compartido, listos para ser enviados si alguien los requiere:
Informe de ^Buttons o ^SystemCheck para cada servidor
Informe de ^Buttons o ^SystemPerformance para cada servidor. Los datos deben abarcar unos cuantos días, incluyendo el día de mayor actividad
Detalles del hardware (proveedor, especificaciones, etc.)
Arquitectura FUTURA (si está prevista su actualización)
Obtener datos similares a los de la arquitectura actual.
2. Contactos
Puede parecer de sentido común, pero es esencial saber quién tiene autoridad para tomar decisiones relacionadas con las actualizaciones, quién está a cargo de qué y a quién hay que contactar en caso de emergencia. Es imprescindible disponer de una tabla sencilla adjunta al documento de actualización.
Nombre
Función
Empresa
Teléfono
Correo electrónico
John
Administrador
ACME
+34222222
John@acme.com
Gary
Desarrollador
ACME
+34222222
Gary@acme.com
Susan
Gestor
ACME
+34222222
susana@acme.com
Centro de Soporte Internacional (WRC)
Soporte de InterSystems (ISC)
InterSystems
+112321321
support@intersytems.com
Dell
Soporte de Dell
Dell
+1xxx
support@delll.com
3. Plan de prueba y detalles
Incluye una descripción de las pruebas realizadas actualmente y las previstas. Las pruebas deben abarcar una actualización completa con un entorno similar. Las pruebas de actualización deberían permitir completar las siguientes secciones sobre la actualización. Si no hay pruebas o no se han completado, deberá anotarse y prepararse para cruzar los dedos. ;-)
4. Principales pasos de la actualización
Debería incluir un resumen sencillo de los diferentes pasos. Por ejemplo:
Stop instances (Prod1 and Prod2)
Copy database folders to new systems (NewProd1 and NewProd2)
Start NewProd1
Compile classes on new Prod1, run update data scripts
Start NewProd2
Check Mirror synch
Assign old IPs
Allow users to connect
5. Detalles del plan
Detalla los pasos anteriores y cómo realizarlos, incluyendo los comandos que se deben ejecutar, los archivos para importar, etc. La mayoría de los pasos no son útiles para empresas externas que no conocen las aplicaciones, y puede que no sea necesario incluirlas cuando se envíe el plan a terceros.
6. Tareas relacionadas
Las actualizaciones, incluyendo los cambios en las aplicaciones, normalmente requieren tareas que hay que realizar antes, durante y después. Se puede crear una sencilla tabla de tareas para controlar todos los pasos, los tiempos, etc.
Un plan detallado pueden incluir cosas como:
Tareas previas a la actualización (ejemplo)
(Detallar todas las tareas necesarias antes de la actualización)
Tarea
Fecha
Estado
Responsable
Notas
Actualizar índices
09/07/22
Hecho
Integración
Actualizar los índices en X para soportar XY
Actualizar el IIS
09/08/22
Pendiente
Administración Web
Actualizar los servidores web antes de realizar la actualización final
Obtener el informe de integridad
09/07/22
Hecho
Administración de Iris
Obtener un informe de integridad de todas las bases de datos
Tareas durante la actualización (ejemplo)
(Detallar las tareas y el plan de actualización paso a paso)
Tarea
Hora (planificada)
Hora (real)
Estado
Responsable
Notas
Parar a los usuarios
06:00
06:10
Pendiente
Integración
Llamar a X para detener las conexiones
Obtener ^SystemCheck
06:10
Pendiente
Administración de Iris
...
..
...
...
Permitir conexiones
09:10
Pendiente
Administración de Iris
Tareas después de la actualización
(Detallar todas las tareas necesarias después de la actualización)
Tarea
Fecha
Estado
Responsable
Notas
Activar ^SystemPerformance
01/01/22
Pendiente
Administración de Iris
Ejecutar el funcionamiento durante unos días
5. Incidencias/Problemas durante la actualización
Escribe los problemas encontrados durante la actualización. Especialmente los problemas que no impidan la actualización. Esta lista puede incluir una falta de comandos necesarios, mensajes de error nuevos, etc. Esta lista te ayudará a mejorar los documentos de actualización futuros y a que no te olvides de arreglar los problemas actuales.
6. Plan de recuperación en caso de desastres / Regresar
Describe el plan en caso de que la actualización falle. Esto debería implicar volver a los antiguos servidores, detener los nuevos, iniciar X, Z, etc. Es útil tener escritos todos los pasos para volver a un escenario de seguridad. Recuerda que el plan de recuperación en caso de desastres normalmente se realiza bajo estrés, y es fácil omitir pasos y olvidar cómo hacerlo o dónde están los archivos, las configuraciones, etc.
Este plan de recuperación debería ser un enlace o ampliación al documento de recuperación existente. Y sino hubiese ninguno, sería un buen momento de crearlo, ya sabéis, la ley de Murphy...
* * *
El objetivo principal de esta guía es que conozcas las dificultades del proceso de actualización y que pienses por adelantado.
Probar, documentar y volver a probar es la garantía de actualizaciones con éxito.
Artículo
Ricardo Paiva · 25 feb, 2021
> **Nota (junio de 2019)**: han cambiado muchas cosas [para obtener los detalles más recientes, haz clic aquí](https://community.intersystems.com/post/unpacking-pbuttons-yape-update-notes-and-quick-guides)
> **Nota (septiembre de 2018)**: ha habido grandes cambios desde que esta publicación apareció por primera vez; sugiero que utilices la versión del contenedor en Docker dado que el proyecto y la información para que se ejecute como un contenedor sigue [publicada en GitHub](https://github.com/murrayo/yape), en el mismo lugar, para que puedas descargarlo, ejecutarlo y modificarlo, si lo necesitas.
Cuando trabajo con clientes en revisiones de rendimiento, planificaciones de capacidad y resolución de problemas, con frecuencia tengo que descomprimir y revisar las métricas del sistema operativo y de caché desde pButtons. En vez de lidiar con los archivos html para cortar y pegar secciones que serán graficadas en Excel, [hace algún tiempo escribí una publicación sobre una herramienta para descomprimir las métricas de pButtons](https://community.intersystems.com/post/extracting-pbuttons-data-csv-file-easy-charting), escrita con el intérprete de unix, perl y los scripts de awk. Si bien este es un *valioso ahorro de tiempo*, no es la historia completa…
También utilizo scripts para realizar automáticamente gráficos de las métricas, para revisiones rápidas y para inconporarlos a informes. Sin embargo, estos gráficos realizados con scripts no se mantienen fácilmente y se vuelven especialmente confusos cuando se necesita una configuración específica del sitio, por ejemplo, una lista de discos para iostat o windows perfmon. Por eso, nunca publiqué las herramientas para hacer gráficos. Pero estoy muy contento de anunciar que ahora existe una solución mucho más sencilla.
El descubrimiento fortuito ocurrió cuando estaba en las instalaciones de un cliente observando el rendimiento del sistema con [ Fabian, y él me mostró lo que hacía para utilizar ciertos módulos de Python para realizar gráficos](https://community.intersystems.com/post/visualizing-data-jungle-part-i-lets-make-graph). Esta es una solución mucho más flexible y fácil de mantener que los scripts que utilizaba. Y la facilidad de integrar los módulos de Python para administrar archivos y gráficos, incluyendo la posibilidad de compartir el html interactivo, significa que el resultado puede ser mucho más útil. Al tomar las publicaciones de Fabian como base, escribí __Yape__ para extraer de forma rápida y sencilla, y luego hacer gráficos de los archivos pButtons de los clientes, en varios formatos. El proyecto se [publicó en GitHub](https://github.com/murrayo/yape) para que puedas descargarlo, ejecutarlo y modificarlo, si lo necesitas.
## Resumen
Actualmente, este proceso consiste en _dos_ pasos.
### Paso 1. `extract_pButtons.py`
Extrae las secciones de interés desde pButtons, y escríbelas en archivos de tipo .csv para abrirlos con Excel o para que se procesen con gráficos utilizando `graph_pButtons.py`.
### Paso 2. `graph_pButtons.py`
Los archivos de gráficos se crearon en el Paso 1. Actualmente las salidas pueden ser gráficas de líneas o de puntos que se representan como `.png` o `.html interactivos` e incluyen opciones de vista panorámica, zoom, impresión, etc.
_Readme.md_ en GitHub tiene especificaciones sobre cómo configurar y ejecutar los dos scripts de Python, y será la referencia más reciente.
## Notas adicionales
Por ejemplo: si utilizas las opciones para agregar prefijos a los directorios de entrada y salida, podrás examinar fácilmente un directorio con un conjunto de archivos pButtons de html (por ejemplo, semanas) y enviarlos a un directorio separado para cada archivo pButtons.
for i in `ls *.html`; do ./extract_pButtons.py $i -p ${i}_; done
for i in `ls *.html`; do ./graph_pButtons.py ./${i}_metrics -p ${i}_; done
A corto plazo, mientras continúo con la serie sobre [cómo planificar y aumentar rendimiento de la plataforma de datos InterSystems IRIS](https://community.intersystems.com/post/intersystems-data-platforms-capacity-planning-and-performance-series-index), utilizaré los gráficos creados con estas herramientas.
Lo probé en OS X, pero no en Windows. No debería tener ningún problema para instalar y ejecutar Python en Windows. Supongo que necesitarás realizar algunos cambios en las barras que se encuentran en la ruta de un archivo. Deja tus comentarios si lo pruebas.
> Nota: hasta hace unas semanas nunca había escrito nada en Python, así que si es un experto en este lenguaje, posiblemente te darás cuenta de que algunas partes del código no siguen las prácticas recomendadas. Sin embargo, utilizo los scripts casi todos los días, por lo que seguiré mejorándolos. Espero perfeccionar mis habilidades en Python, ¡pero siéntete libre de “educarme” si ves algo que debería corregirse!
Si descubres que los scripts te resultan útiles, házmelo saber y vuelve con frecuencia para obtener nuevas características y actualizaciones.