¿Que diria usted si yo le dijera que es posible hacer un sistema de monitoreo usando Java, ONCR RPC y el protocolo Jabber? Es mucho más sencillo de lo que usted cree y es algo que siempre quise hacer en la compañia en la cual trabajaba anteriormente (en la actual estoy trabajando similar, pero mucho más sofisticado :D) y nunca tuve tiempo de hacer.
Pero bueno, demasiada habladera de paja, veamos que hay que poner en orden:
- Asumimos que los servicios que queremos monitorear se basan en ONRC RPC. Ejemplo de ellos son NIS, NFS, Rusers, Rquota y en nuestro caso Rstatd que aunque es muchisimo más limitado que SNMP está presente en muchos servidores, corriendo de grátis 🙂
- ONRC RPC tiene unos archivos que describen como usar los servicios remoto, llamados ‘stubs’. Si alguna vez ha utilizado RMI el concepto le será familiar. Pero hay un problema, los ‘stubs’ son muy similares a el lenguaje C, por lo que necesitamos una herramienta que nos sirva como puente. Para ello usamos el compilador de remote tea: jrpcgen.jar.
- Una vez obtenidos las interfaces y clientes en Java entonces las compilamos y nos valemos de las librerias de comunicación provistas por remote tea (onrpc.jar).
- De allí armamos los programas que van a hacer la consulta por medio de RPC. Luego nos preocupamos de como enviar los datos usando Jabber.
Lo primero que hay que hacer es generar las interfaces. El precompilador jrpcgen no tiene una tarea de Ant, sin embargo nosotros nos valemos de un macro con el cual preparamos las fuentes. Note que primero limpiamos los archivos ‘.x’ utilizando al precompilador cpp y es allí cuando podemos usar a jrpcgen.
El macro en Ant es muy sencillo:
–>
62: <!-- Special macro to enable the RPC stub generation --> 63: <macrodef name="rpcgen"> 64: <attribute name="xfile"/> 65: <attribute name="clientJavaFile"/> 66: <sequential> 67: <!-- Preprocess the 'x' files --> 68: <exec 69: executable="/usr/bin/cpp"> 70: <arg line="${rpc.include.local}/@{xfile}.x"/> 71: <arg line="${rpc.include.local}/@{xfile}.cpp"/> 72: </exec> 73: <!-- Cleanup remaining artifacts --> 74: <replaceregexp 75: file="${rpc.include.local}/@{xfile}.cpp" 76: match="^#" 77: replace="//" 78: byline="true"/> 79: <!-- generate the Java classes --> 80: <java 81: classname="org.acplt.oncrpc.apps.jrpcgen.jrpcgen" 82: dir="${rpc.include.local}" 83: fork="true"> 84: <classpath refid="path.classpath"/> 85: <arg value="-p"/> 86: <arg value="${package.tree}"/> 87: <arg value="-nobackup"/> 88: <arg value="-noserver"/> 89: <arg value="-verbose"/> 90: <arg value="-withcallinfo"/> 91: <arg value="-bean"/> 92: <arg value="-c"/> 93: <arg value="@{clientJavaFile}"/> 94: <arg value="-d"/> 95: <arg value="${stub}"/> 96: <arg value="@{xfile}.cpp"/> 97: </java> 98: </sequential> 99: </macrodef>
Una vez definido llamarlo es muy sencillo (note que primero generamos el código Java de las interfaces y luego más delante compilamos el código de la aplicación junto con el de las interfaces):
–>
101: <target 102: name="rpcstub" 103: depends="init" 104: description="Create the RPC Java stub objects"> 105: <copy 106: todir="${rpc.include.local}" 107: overwrite="true"> 108: <fileset dir="${rpc.include.dir}"> 109: <include name="*.x"/> 110: <include name="*.h"/> 111: </fileset> 112: </copy> 113: <rpcgen xfile="rstat" clientJavaFile="RstatClient"/> 114: <rpcgen xfile="mount" clientJavaFile="MountClient"/> 115: <rpcgen xfile="nfs_prot" clientJavaFile="NfsProtClient"/> 116: </target>
La compilación no depende de la generación de las interfaces ya que estas se guardan en CVS y si por algunas razón cambian es entonces que volverán a ser creadas. Es decir, una vez creadas las interfaces solamente nos tenemos que preocupar por compilar:
–>
118: <target 119: name="build" 120: depends="init" 121: description="Compile the Java source code"> 122: <!-- Compile the stub --> 123: <javac 124: srcdir="${stub}" 125: destdir="${build}" 126: deprecation="true" 127: optimize="false" 128: debug="true"> 129: <include name="**/*.java"/> 130: <classpath refid="path.classpath"/> 131: </javac> 132: <!-- Compile the app --> 133: <javac 134: srcdir="${src}/main" 135: destdir="${build}" 136: deprecation="true" 137: optimize="false" 138: debug="true"> 139: <include name="${package.path}/**/*.java"/> 140: <classpath refid="path.classpath"/> 141: </javac> 142: <copy 143: todir="${build}" 144: overwrite="true"> 145: <fileset dir="${properties}/main"> 146: <include name="${package.path}/**/*.properties"/> 147: </fileset> 148: </copy> 149: <copy 150: todir="${build}" overwrite="true"> 151: <fileset dir="."> 152: <include name="log4j.properties"/> 153: </fileset> 154: </copy> 155: </target>
El siguiente paso es preparar las pruebas de unidad con Junit. Lo primero que hacemos es decirle a ant que es lo que queremos ejecutar y en que orden (si no está familiarizado con Junit, le recomiendo que busque una de las tantas referencias que hay en Internet):
–>
205: <target 206: name="test" 207: depends="build" 208: description="Unit tests"> 209: <echo>Running unit tests</echo> 210: <javac 211: srcdir="${src}/test" 212: destdir="${test}" 213: includes="${package.path}/**/Test*" 214: deprecation="true" 215: optimize="false" 216: debug="true"> 217: <classpath refid="path.test"/> 218: </javac> 219: 220: <junit 221: fork="yes" 222: printsummary="on" 223: maxmemory="300m"> 224: <classpath refid="path.test"/> 225: <formatter type="xml" /> 226: <formatter type="plain" /> 227: <!-- 228: - As root, set the following property on the /etc/exports file: 229: /usr/local/src 127.0.0.1/255.0.0.0(insecure,ro) 230: - Then: 231: /etc/init.d/nfs start 232: --> 233: <sysproperty 234: key="test.nfs.dir" 235: value="/usr/local/src"/> 236: 237: <sysproperty 238: key="test.nfs.server" 239: value="localhost"/> 240: 241: <sysproperty 242: key="test.rstat.server" 243: value="localhost"/> 244: 245: <!-- Wait 35 seconds --> 246: <sysproperty 247: key="test.rstat.wait" 248: value="35000"/> 249: 250: <test 251: name="${testcase}" 252: todir="${test}" 253: if="testcase"/> 254: 255: <batchtest 256: todir="${test}" 257: unless="testcase"> 258: <fileset 259: dir="${test}" 260: includes="**/Test*.class"/> 261: </batchtest> 262: </junit> 263: 264: <junitreport todir="${test}"> 265: <fileset dir="${test}"> 266: <include name="TEST-*.xml"/> 267: </fileset> 268: <report format="frames" todir="${test}"/> 269: </junitreport> 270: </target>
Es entonces cuando podremos utilizar las clases cliente. Me gusta probar las cosas antes de ponerlas juntas, así que escribí algunas pruebas de unidad (Unit tests) con JUnit vemos que tan bien o mal están las clases generadas:
–>
1:package com.kodegeek.blog.monitoring.rpc; 2: 3:import java.net.InetAddress; 4:import java.util.ResourceBundle; 5: 6:import junit.framework.Assert; 7:import junit.framework.TestCase; 8: 9:import org.acplt.oncrpc.OncRpcClientAuthUnix; 10:import org.acplt.oncrpc.OncRpcProtocols; 11: 12:public class TestRpcStubs extends TestCase { 13: 14: /** 15: * Default max timeout value, in miliseconds 16: */ 17: public static final int MAX_TIMEOUT = 1000*60; 18: 19: private static ResourceBundle NFS_BUNDLE; 20: private static ResourceBundle RSTAT_BUNDLE; 21: 22: { 23: NFS_BUNDLE = ResourceBundle.getBundle(NfsPing.class.getName()); 24: RSTAT_BUNDLE = ResourceBundle.getBundle(RstatPing.class.getName()); 25: } 26: 27: protected void setUp() throws Exception { 28: super.setUp(); 29: System.out.println("Make sure all the services are running with '/usr/sbin/rpcinfo -p'"); 30: } 31: 32: /** 33: * Simple test for the NFS client, using the generated RPC stubs directly 34: * 35: */ 36: public void testNfs() { 37: MountClient mount = null; 38: exports exportsList = null; 39: fhstatus fhStatus = null; 40: OncRpcClientAuthUnix authUnix = null; 41: try { 42: authUnix = new OncRpcClientAuthUnix( 43: System.getProperty("test.nfs.server"), 44: Integer.parseInt(NFS_BUNDLE.getString("NfsPing.user.id")), 45: Integer.parseInt(NFS_BUNDLE.getString("NfsPing.user.group")) 46: ); 47: Assert.assertNotNull("Auth Unix is null", authUnix); 48: // Get the list of exported directories 49: mount = new MountClient( 50: InetAddress.getByName(System.getProperty("test.nfs.server")), 51: OncRpcProtocols.ONCRPC_TCP); 52: Assert.assertNotNull("Mount is null", mount); 53: exportsList = mount.MOUNTPROC_EXPORT_1(); 54: Assert.assertNotNull("Export list is null", exportsList); 55: exportnode node = exportsList.value; 56: Assert.assertNotNull("No nodes are being exported", node); 57: while (node != null) { 58: Object [] nodeForm = new Object[1]; 59: nodeForm[0] = node.ex_dir.value; 60: System.out.printf( 61: NFS_BUNDLE.getString("NfsPing.msg.exportedFs"), nodeForm 62: ); 63: 64: mount.getClient().setAuth(authUnix); 65: mount.getClient().setTimeout(MAX_TIMEOUT); 66: 67: fhStatus = mount.MOUNTPROC_MNT_1(new dirpath(System.getProperty("test.nfs.dir"))); 68: if (fhStatus == null) { 69: Assert.fail("unable to get fhStatus for: " +System.getProperty("test.nfs.dir")); 70: } 71: // Get the next node 72: node = node.ex_next.value; 73: } 74: } catch (Throwable throwbl) { 75: Assert.fail("Got an exception: " + throwbl.toString()); 76: throwbl.printStackTrace(); 77: } finally { 78: // Empty for now 79: } 80: } 81: 82: /** 83: * Simple test for Rstat, using the generated RPC stubs directly 84: * Timestamps are separated into seconds (standard UNIX time) and 85: * microseconds. The availability of a current timestamp allows proper 86: * calculation of the interval between measurements without worrying 87: * about network latency. 88: * 89: * Most values are counters. To get the real numbers you have to 90: * fetch() samples regularly and divide the counter increments 91: * by the time interval between the samples. 92: * 93: * The cpu_time array holds the ticks spent in the various CPU states 94: * (averaged over all CPUs). If you know the regular tick rate of the target 95: * system you may calculate the number of CPUs from the sum of C<cpu_time> 96: * increments and the time interval between the samples. Most often you 97: * will be interested in the percentage of CPU states only. 98: */ 99: public void testRstat() { 100: OncRpcClientAuthUnix authUnix = null; 101: RstatClient rstat = null; 102: statstime stats = null; 103: statstime stats2 = null; 104: try { 105: authUnix = new OncRpcClientAuthUnix( 106: System.getProperty("test.rstat.server"), 107: Integer.parseInt(RSTAT_BUNDLE.getString("RstatPing.user.id")), 108: Integer.parseInt(RSTAT_BUNDLE.getString("RstatPing.user.group")) 109: ); 110: Assert.assertNotNull("Auth Unix is null", authUnix); 111: 112: rstat = new RstatClient( 113: InetAddress.getByName(System.getProperty("test.rstat.server")), 114: OncRpcProtocols.ONCRPC_UDP 115: ); 116: Assert.assertNotNull("Rstat is null", rstat); 117: rstat.getClient().setAuth(authUnix); 118: rstat.getClient().setTimeout(MAX_TIMEOUT); 119: stats = rstat.RSTATPROC_STATS_3(); 120: Assert.assertNotNull("statsswtch stats is null", stats); 121: 122: // Now wait a little bit before taking the next snapshot 123: long wait = Long.parseLong(System.getProperty("test.rstat.wait")); 124: Thread.sleep(wait); 125: 126: // Get another snapshot 127: stats2 = rstat.RSTATPROC_STATS_3(); 128: 129: long timeElaps = stats2.getCurtime().tv_sec -stats.getCurtime().tv_sec; 130: 131: Object [] statColl = new Object[23]; 132: statColl[0] = new Long((stats2.if_collisions -stats.if_collisions)/ timeElaps); 133: statColl[1] = new Long((stats2.if_ierrors -stats.if_ierrors) /timeElaps); 134: statColl[2] = new Long((stats2.if_ipackets -stats.if_ipackets) /timeElaps); 135: statColl[3] = new Long((stats2.if_oerrors -stats.if_oerrors) /timeElaps); 136: statColl[4] = new Long((stats2.if_opackets -stats.if_opackets) /timeElaps); 137: statColl[5] = new Long((stats2.v_intr -stats.v_intr) /timeElaps); 138: statColl[6] = new Long((stats2.v_pgpgin -stats.v_pgpgin) /timeElaps); 139: statColl[7] = new Long((stats2.v_pgpgout -stats.v_pgpgout) /timeElaps); 140: statColl[8] = new Long((stats2.v_pswpin -stats.v_pswpin) /timeElaps); 141: statColl[9] = new Long((stats2.v_pswpout -stats.v_pswpout) /timeElaps); 142: statColl[10] = new Long((stats2.v_swtch -stats.v_swtch) /timeElaps); 143: statColl[11] = new Double(stats2.getAvenrun(0) / 256.0); 144: statColl[12] = new Double(stats2.getAvenrun(1) / 256.0); 145: statColl[13] = new Double(stats2.getAvenrun(2) / 256.0); 146: statColl[14] = new Long((stats2.getCp_time(0) -stats.getCp_time(0))/ timeElaps); 147: statColl[15] = new Long((stats2.getCp_time(1) -stats.getCp_time(1)) / timeElaps); 148: statColl[16] = new Long((stats2.getCp_time(2) -stats.getCp_time(2)) / timeElaps); 149: statColl[17] = new Long((stats2.getCp_time(3) -stats.getCp_time(3))/ timeElaps); 150: statColl[18] = new Long((stats2.getDk_xfer(0) -stats.getDk_xfer(0))/ timeElaps); 151: statColl[19] = new Long((stats2.getDk_xfer(1) -stats.getDk_xfer(1)) / timeElaps); 152: statColl[20] = new Long((stats2.getDk_xfer(2) -stats.getDk_xfer(2)) / timeElaps); 153: statColl[21] = new Long((stats2.getDk_xfer(3) -stats.getDk_xfer(3)) / timeElaps); 154: statColl[22] = new Long(stats2.getBoottime().tv_sec); 155: 156: StringBuffer info = new StringBuffer(); 157: info.append("Rstat unit test\n"); 158: info.append("Time elapsed between measures (seconds): " +timeElaps + "\n"); 159: info.append("if_collisions=%s\n"); 160: info.append("if_ierrors=%s\n"); 161: info.append("if_ipackets=%s\n"); 162: info.append("if_oerrors=%s\n"); 163: info.append("if_opackets=%s\n"); 164: info.append("v_intr=%s\n"); 165: info.append("v_pgpgin=%s\n"); 166: info.append("v_pgpgout=%s\n"); 167: info.append("v_pswpin=%s\n"); 168: info.append("v_pswpout=%s\n"); 169: info.append("v_swtch=%s\n"); 170: info.append("Avenrun (CPU load) 0=%s\n"); 171: info.append("Avenrun (CPU load) 1=%s\n"); 172: info.append("Avenrun (CPU load) 2=%s\n"); 173: info.append("CP_time 0 Usr=%s\n"); 174: info.append("CP_time 1 System=%s\n"); 175: info.append("CP_time 2 Wio=%s\n"); 176: info.append("CP_time 3 Idle=%s\n"); 177: info.append("Dk_xfer 0=%s\n"); 178: info.append("Dk_xfer 1=%s\n"); 179: info.append("Dk_xfer 2=%s\n"); 180: info.append("Dk_xfer 3=%s\n"); 181: info.append("Raw uptime in seconds=%s\n"); 182: 183: // Spit the results out 184: System.out.printf( 185: info.toString(), 186: statColl 187: ); 188: } catch (Throwable throwbl) { 189: Assert.fail("Got an exception: " + throwbl.toString()); 190: throwbl.printStackTrace(); 191: } finally { 1 92: // Empty for now 193: } 194: } 195:}
El resultado de las pruebas de unidad es el siguiente:
Make sure all the services are running with '/usr/sbin/rpcinfo -p'Exported filesystem: /usr/local/srcMake sure all the services are running with'/usr/sbin/rpcinfo -p'Rstat unit testTime elapsed between measures (seconds): 37if_collisions=0if_ierrors=0if_ipackets=4if_oerrors=0if_opackets=4v_intr=1015v_pgpgin=0v_pgpgout=0v_pswpin=0v_pswpout=0v_swtch=775Avenrun (CPU load) 0=1.4296875Avenrun (CPU load) 1=0.94921875Avenrun (CPU load) 2=0.6875CP_time 0 Usr=29CP_time 1 System=0CP_time 2 Wio=2CP_time 3 Idle=61Dk_xfer 0=0Dk_xfer 1=0Dk_xfer 2=0Dk_xfer 3=0Raw uptime in seconds=1150929416
El siguiente paso es probar como enviar información usando el protocolo de Jabber, pero eso se los mostraré en un siguiente articulo 😉
El sistema no es de grado comercial; Uno de verdad debería estar en capacidad de monitorear varios servidores a la vez, y si debe escalar entonces quizas usar Jabber como presentación quizas no sea lo adecuado. Pero nadie niega que fué divertido 😉
Referencias:
- IBM RedBook System Management API for z/VM
- Remote Tea website, API
Por ahora no pienso colocar el código para que se lo baje, aunque si lo puede revizar utilizando la interfaz web a CVS.
Buscar en Technorati: venezuela, java, linux, Open Source, rpc, jabber
Buscar en Blogalaxia: venezuela, java, linux, Open Source, rpc, jabber