Para que una aplicación gráfica guste, lo primero es que funcione bien, pero lo segundo y en este órden es que sea agradable a la vista y no parezca que se arrastra...
Por eso he estado implementando que algunas acciones de TcosMonitor se ejecuten en hilos (Threads) para que la interfaz no se congele, todavía no funciona todo lo bien que quisiera pero la fluided se nota bastante.
Por ejemplo, el descubrimiento de equipos, es decir, las máquinas conectadas al puerto 6000 del servidor (realmente a 6000-6009 porque parece que LTSP si se configura startx en un screen distinto del 0 se conecta a otro puerto... )
Esto es el código que se ejecuta al pulsar el botón de actualizar:
def on_refreshbutton_click(self,widget):
if self.init.searching:
print_debug( "on_refreshbutton_click() searching TRUE" )
# FIXME show a msg
return
self.write_into_statusbar ( _("Searching for connected hosts...") )
allclients=self.localdata.GetAllClients()
if len(allclients) == 0:
self.write_into_statusbar ( _("Not connected hosts found.") )
return
else:
txt=_("Found %d hosts, retrieving data..." ) %len(allclients)
self.write_into_statusbar ( txt )
# populate_list in a thread
Thread( target=self.init.populate_hostlist, args=([allclients]) ).start()
return
La acción que más tiempo lleva es tomar usuario, nombre de host, número de procesos, tiempo conectado, y estado del bloqueo de pantalla para cada equipo, por lo que esto se ejecuta en un nuevo Thread (línea negrita), siendo el trozo subrallado la función que hace todas estas cosas.
La función populate_hostlist de la clase Initialize es la siguiente:
def populate_hostlist(self, clients):
print_debug ( "populate_hostlist() init" )
start1=time()
if self.searching:
return
self.searching=True
# clean list
print_debug ( "populate_hostlist() clear list!!!" )
self.model.clear()
COL_HOST, COL_IP, COL_USERNAME, COL_ACTIVE, COL_LOGGED, COL_BLOCKED, COL_PROCESS, COL_TIME = range(8)
inactive_image = gtk.gdk.pixbuf_new_from_file(shared.GLADE_DIR + '/images/inactive.png')
active_image = gtk.gdk.pixbuf_new_from_file(shared.GLADE_DIR + '/images/active.png')
logged_image = gtk.gdk.pixbuf_new_from_file(shared.GLADE_DIR + '/images/logged.png')
unlogged_image = gtk.gdk.pixbuf_new_from_file(shared.GLADE_DIR + '/images/unlogged.png')
locked_image = gtk.gdk.pixbuf_new_from_file(shared.GLADE_DIR + '/images/locked.png')
unlocked_image = gtk.gdk.pixbuf_new_from_file(shared.GLADE_DIR + '/images/unlocked.png')
i=0
for host in clients:
i += 1
self.main.localdata.newhost(host)
self.main.xmlrpc.newhost (host)
ip=host
hostname=self.main.localdata.GetHostname(ip)
username=self.main.localdata.GetUsername(ip)
num_process=self.main.localdata.GetNumProcess(ip)
time_logged=self.main.localdata.GetTimeLogged(ip)
if self.main.localdata.IsActive(ip):
image_active=active_image
else:
image_active=inactive_image
if self.main.localdata.IsLogged(ip):
image_logged=logged_image
else:
image_logged=unlogged_image
if self.main.localdata.IsBlocked(host):
image_blocked=locked_image
else:
image_blocked=unlocked_image
gtk.threads_enter()
self.iter = self.model.append (None)
self.model.set_value (self.iter, COL_HOST, hostname )
self.model.set_value (self.iter, COL_IP, host )
self.model.set_value (self.iter, COL_USERNAME, username )
self.model.set_value (self.iter, COL_ACTIVE, image_active )
self.model.set_value (self.iter, COL_LOGGED, image_logged)
self.model.set_value (self.iter, COL_BLOCKED, image_blocked)
self.model.set_value (self.iter, COL_PROCESS, num_process )
self.model.set_value (self.iter, COL_TIME, time_logged)
gtk.threads_leave()
crono(start1, "populate_host_list(%s)" %(ip) )
print_debug ( "populate_hostlist() END" )
self.searching=False
return
El código en negrita es lo más crítico de la función ya que accede al interfaz gráfico GTK para completar los valores del TreeView, por eso van encerrados entre llamadas a gtk.threads_{enter,leave}. La función crono no es más que un cronómetro que resta dos variables de tiempo para saber cuando se tarda en ejecutar el proceso, si se desactiva el modo debug hay muchas cosas que ya no se ejecutan como ésta.
Como se puede adivinar self.searching es un bloqueo para que no se llame a esta función si ya se está ejecutando.
Funciona bastante bien pero de vez en cuando y aleatoriamente da una extraña violación de segmento acompañada de un fallo de tubería de wc, que me da que pensar que algo falla en la extracción del número de procesos:
cmd=" ps aux|grep "^%s "| wc -l" %(self.username)
He añadido un par de condiciones a la función que lo hace y parece que ya no ha vuelto a suceder.
También estoy implementando un control de cache de los datos, por ejemplo, guardar en un array si el equipo está vivo (responde a los ping), los puertos abiertos, usuario y nombre de equipo, para que sólo se calculen la primera vez y el resto se devuelvan desde cache, para evitar problemas cada elemento del array (cada host) tiene un tiempo de vida que si se supera se vuelve a pedir, (configurable desde las preferencias).
Para terminar unas capturas de la interfaz un poco más bonita con sus iconos de menús, con una red simple de 3 equipos funcionando sobre 3 vmplayer en mi pobre portátil:
- tcos1 es un equipo basado en TCOS
- tcos2 es un equipo basado en LTSP 4.2
- tcos3 es un equipo basado en PXES 1.2
Con esto se demuestra que TcosMonitor ya funciona medianamente en las tres plataformas.



ATENCIÓN:
Los paquetes los he partido para no tener que instalar cosas que no vamos a usar:
tcosmonitor - interfaz pyhton
tcos-tcosmonitor - complementos para que tcosmonitor funcione con tcos
pxes-1.0-tcosmonitor - complementos para que tcosmonitor funcione con pxes-1.0
pxes-1.1-tcosmonitor - complementos para que tcosmonitor funcione con pxes-1.1
pxes-1.2-tcosmonitor - complementos para que tcosmonitor funcione con pxes1.2
ltsp-tcosmonitor - complementos para que tcosmonitor funcione con LTSP (instalado en /opt/ltsp)
El mirror donde siempre:
deb http://soleup.eup.uva.es/tcos/debian unstable main