whoall 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. #!/usr/bin/python
  2. # - *- coding: utf- 8 - *-
  3. #
  4. # Python whoall: parallel, multi-threaded version
  5. # First author: jacopogh - nov 2006
  6. # Modified and updated by admins & 150 over time
  7. #
  8. # Last modified: March 2016 - blue
  9. from time import time
  10. start = time()
  11. import argparse
  12. import os
  13. import telnetlib
  14. from collections import Counter
  15. from threading import Thread
  16. from socket import error
  17. from sys import exit,stdout
  18. ### ARGUMENT PARSING ###
  19. # Define parser
  20. parser = argparse.ArgumentParser(
  21. epilog='Please, report bugs or unwanted behavior to: \
  22. working@lcm.mi.infn.it' )
  23. # Define optional arguments
  24. parser.add_argument('-1', '--lcm1', action='store_true', dest='lcm1',
  25. help='only display LCM1 hosts ')
  26. parser.add_argument('-2', '--lcm2', action='store_true', dest='lcm2',
  27. help='only display LCM2 hosts ')
  28. parser.add_argument('-l', '--laur', action='store_true', dest='laur',
  29. help='only display LAUR hosts ')
  30. parser.add_argument('-c', '--condor', action='store_true', dest='condor',
  31. help='only display condor nodes ')
  32. parser.add_argument('-C', '--cuda', action='store_true', dest='cuda',
  33. help='only display CUDA hosts ')
  34. parser.add_argument('-f', '--full', action='store_true', dest='full',
  35. help='display all nodes including empty, \
  36. unreachable and unavailable')
  37. parser.add_argument('-m', '--math', action='store_true', dest='math',
  38. help='only Mathematica hosts ')
  39. parser.add_argument('-n', action='store_true', dest='n',
  40. help='not display the progressbar and colors')
  41. parser.add_argument('-N', action='store_true', dest='N',
  42. help='also display number of logs')
  43. parser.add_argument('-v', '--version', action='version',
  44. version='%(prog)s 2.1', help='Print program version')
  45. # Parse arguments
  46. args = parser.parse_args()
  47. # Define port on which the lcm_w service is listening
  48. # NB at the time of writing lcm_w is managed by inetd
  49. PORT = 79
  50. #############################################
  51. # Node class
  52. # Each node executes a `who` query in a separate thread
  53. #############################################
  54. class Node(Thread):
  55. # Constructor
  56. def __init__(self, name, location, math4=False, math5=False,
  57. cuda=False, condor=False, port=PORT):
  58. # Fork the thread first thing
  59. Thread.__init__(self)
  60. # Variables initialization
  61. self.hostname = name
  62. self.location = location
  63. if math4:
  64. self.math_version = 'M4'
  65. elif math5:
  66. self.math_version = 'M5'
  67. else:
  68. self.math_version = 'NA'
  69. self.cuda = cuda
  70. self.condor = condor
  71. self.local = []
  72. self.remote = []
  73. self.lgod = []
  74. self.rgod = []
  75. # Former admins
  76. self.chuck = []
  77. # Node status
  78. self.up = True
  79. self.avail = True
  80. self.port = port
  81. self.timeout = False
  82. # Ping the host to see if it's up
  83. def isup(self):
  84. # Is the host up?
  85. ping = os.popen("ping -w1 -c1 "+self.hostname,"r")
  86. if "0 received" in ping.read():
  87. self.up = False
  88. return False
  89. else:
  90. return True
  91. # Open telnet connection
  92. def connect(self):
  93. # Try to connect first
  94. try:
  95. self.conn = telnetlib.Telnet(self.hostname,self.port)
  96. # Inetd is down!
  97. except error:
  98. self.avail = False
  99. return False
  100. return True
  101. # Read lcm_w ouput and fill users-gods lists accordingly
  102. def read(self):
  103. # Read data
  104. lista=''
  105. reachable=0
  106. try: lista = self.conn.read_until("EOF",1)
  107. except EOFError: reachable=1
  108. del self.conn
  109. if lista=='':
  110. if reachable == 0:
  111. self.timeout=True
  112. # Split lines and fields
  113. righe = lista.splitlines()
  114. # Needed later for isempty
  115. self.users = len(righe)
  116. # Local-remote-god user check
  117. for x in righe:
  118. fields = x.split()
  119. if "tty" in fields[1]:
  120. if fields[0] in gods:
  121. self.lgod.append(fields[0].lower())
  122. else:
  123. if fields[0] not in chucknorris:
  124. self.local.append(fields[0].lower())
  125. else:
  126. if fields[0] in gods:
  127. if fields[0] not in self.lgod:
  128. self.rgod.append(fields[0].lower())
  129. else:
  130. if fields[0] not in self.local:
  131. if fields[0] not in chucknorris:
  132. self.remote.append(fields[0].lower())
  133. # Former admins
  134. if fields[0] in chucknorris:
  135. self.chuck.append(fields[0].lower())
  136. # Run method
  137. # This is the part that every thread executes in parallel
  138. # If host is up -> open connection -> if connection -> read data
  139. def run(self):
  140. if self.isup():
  141. if self.connect():
  142. self.read()
  143. # Is host empty?
  144. def isempty(self):
  145. if (self.users > 0):
  146. return False
  147. else:
  148. return True
  149. # Print hostname (plus qualifiers if needed)
  150. def qualifiedname(self):
  151. tmp = self.hostname
  152. # Add math version to hostname if required
  153. if args.math:
  154. tmp += '[' + self.math_version + ']'
  155. # Add cuda tag [C] to hostname for CUDA hosts (jp)
  156. # FIXME I don't think we need this. Cuda nodes have their own section
  157. # if self.cuda:
  158. # self.hostname += ' '*(5-len(self.hostname))+'[C]'
  159. return tmp
  160. # Print output giorgio-style, host based (after threads have finished)
  161. def printlist(self):
  162. # Uniq equivalent and string conversion
  163. strlocal = ""
  164. strremote = ""
  165. strlgod = ""
  166. strrgod = ""
  167. strchuck = ""
  168. if args.N:
  169. # Also print how many times users are logged (jp+blue)
  170. lcounter = Counter(self.local)
  171. for user in lcounter:
  172. strlocal += user + '(' + str(lcounter[user]) + ')'
  173. rcounter = Counter(self.remote)
  174. for user in rcounter:
  175. strremote += user + '(' + str(rcounter[user]) + ') '
  176. lgcounter = Counter(self.lgod)
  177. for user in lgcounter:
  178. strlgod += user + '(' + str(lgcounter[user]) + ') '
  179. rgcounter = Counter(self.rgod)
  180. for user in rgcounter:
  181. strrgod += user + '(' + str(rgcounter[user]) + ') '
  182. ccounter = Counter(self.chuck)
  183. for user in ccounter:
  184. strchuck += user + '(' + str(ccounter[user]) + ') '
  185. else:
  186. # A set cannot have duplicate entries: uniq equivalent
  187. for item in set(self.local):
  188. strlocal += str(item) + ' '
  189. for item in set(self.remote):
  190. strremote += str(item) +' '
  191. for item in set(self.lgod):
  192. strlgod += str(item) +' '
  193. for item in set(self.rgod):
  194. strrgod += str(item) +' '
  195. for item in set(self.chuck):
  196. strchuck += str(item) +' '
  197. # Print out hostname and connected users
  198. print ' ' + self.qualifiedname() + ' '*(15-len(self.qualifiedname())) \
  199. + red + strlgod + normal + pink + strrgod + normal + green \
  200. + strlocal + normal + blue + strremote + normal \
  201. + turquoise + strchuck + normal
  202. ### end class Node
  203. ### USER GROUPS ###
  204. # Current admins/150
  205. gods = ['root','andreatsh','andreamalerba','lorenzouboldi',
  206. 'stefanobalzan','eugeniothieme']
  207. # Former admins: bother them at your own risk
  208. chucknorris = [ 'agalli', 'ikki', 'buddino', 'alex', 'ema', 'ktf',
  209. 'davideg', 'jacopogh', 'lampo', 'gian', 'rbondesan',
  210. 'scolari', 'emanueleb', 'giani_matteo','gabryv','fran',
  211. 'alqahirah','giorgio_ruffa','palazzi','algebrato','blanc',
  212. 'blue','silviacotroneo']
  213. ### HOST LIST ###
  214. # Only edit here to add/remove/change hostlist
  215. nodes = [
  216. Node('abe', 'LCM1', math5=True, condor=True),
  217. Node('crash', 'LCM1', math4=True, condor=True),
  218. Node('duke', 'LCM1', math4=True, condor=True),
  219. Node('glados', 'LCM1', math5=True, condor=True),
  220. Node('lara', 'LCM1', condor=True),
  221. Node('link', 'LCM1', math5=True, condor=True),
  222. Node('king', 'LCM1', math5=True, condor=True),
  223. Node('pang', 'LCM1', math5=True, condor=True),
  224. Node('pong', 'LCM1', math5=True, condor=True),
  225. Node('snake', 'LCM1', math5=True, condor=True),
  226. Node('sonic', 'LCM1', math4=True, condor=True),
  227. Node('spyro', 'LCM1', math5=True, condor=True),
  228. Node('yoshi', 'LCM1', math5=True, condor=True),
  229. Node('actarus', 'LCM2', math4=True, condor=True),
  230. Node('elwood', 'LCM2', math4=True, condor=True),
  231. Node('gex', 'LCM2', condor=True),
  232. Node('gin', 'LCM2', math4=True, condor=True),
  233. Node('jake', 'LCM2', math4=True, condor=True),
  234. Node('kirk', 'LCM2', math4=True, condor=True),
  235. Node('martini', 'LCM2', math4=True, condor=True),
  236. Node('picard', 'LCM2', math4=True, condor=True),
  237. Node('q', 'LCM2', math4=True, condor=True),
  238. Node('raziel', 'LCM2', math4=True, condor=True),
  239. Node('sarek', 'LCM2', math4=True, condor=True),
  240. Node('spock', 'LCM2', condor=True),
  241. Node('tron', 'LCM2', math4=True, condor=True),
  242. Node('worf', 'LCM2', math4=True, condor=True),
  243. Node('zombie', 'LCM2', math4=True, condor=True),
  244. Node('eskimo', 'LAUR'),
  245. Node('orion', 'LAUR'),
  246. Node('tilde', 'LAUR'),
  247. Node('jacobi', 'CUDA', cuda=True),
  248. Node('yukawa', 'CUDA', cuda=True),
  249. Node('tesla', 'CUDA', cuda=True),
  250. ]
  251. ### SECTION SPLITTERS ###
  252. if args.n:
  253. splitters = { 'LCM1': '-LCM1----',
  254. 'LCM2': '-LCM2----',
  255. 'CUDA': '-CUDA----',
  256. 'LAUR': '-LAUR----' }
  257. else:
  258. splitters = { 'LCM1': '-\033[1;36mLCM1\033[0m----',
  259. 'LCM2': '-\033[1;32mLCM2\033[0m----',
  260. 'CUDA': '-\033[1;35mCUDA\033[0m----',
  261. 'LAUR': '-\033[1;33mLAUR\033[0m----' }
  262. ###################################################
  263. # Main
  264. ###################################################
  265. # Set terminal colors
  266. if not args.n:
  267. red = "\033[1;31m"
  268. normal = "\033[0m"
  269. blue = "\033[1;34m"
  270. pink = "\033[1;35m"
  271. green = "\033[1;32m"
  272. turquoise ="\033[1;36m"
  273. else:
  274. red = ""
  275. normal = ""
  276. blue = ""
  277. pink = ""
  278. green = ""
  279. turquoise =""
  280. # Check whether we want a progressbar displayed
  281. progressbar = not args.n
  282. # Filter hostlist according to arguments
  283. if args.lcm1:
  284. nodes = [ node for node in nodes if node.location == 'LCM1' ]
  285. if args.lcm2:
  286. nodes = [ node for node in nodes if node.location == 'LCM2' ]
  287. if args.laur:
  288. nodes = [ node for node in nodes if node.location == 'LAUR' ]
  289. if args.cuda:
  290. nodes = [ node for node in nodes if node.location == 'CUDA' ]
  291. if args.math:
  292. nodes = [ node for node in nodes if node.math_version != 'NA' ]
  293. # Number of hosts (for progressbar)
  294. num = len(nodes)
  295. # Start the threads
  296. for node in nodes:
  297. node.start()
  298. # Used for progressbar
  299. index = 0
  300. print ' Querying ' + str(num) + ' hosts...'
  301. # Rejoin them when their work is done
  302. # NB a progress bar does not make much sense if join is not asynchronous (blue)
  303. for node in nodes:
  304. node.join()
  305. if progressbar:
  306. # Progessbar
  307. index += 1
  308. stdout.write('\r ['
  309. + '='*index
  310. + '>'*(1-int(index/num))
  311. + ' '*(num-index-1) + ']')
  312. stdout.flush()
  313. if progressbar:
  314. # Newline
  315. print '\n Done... ( %(t).3f s)' % {'t': (time() - start)}
  316. if args.full or args.math or args.cuda:
  317. # Save down/unreachable/unavailable nodes to separate lists
  318. downlist = ''
  319. timeoutlist = ''
  320. unavlist = ''
  321. emptylist = ''
  322. for node in nodes:
  323. if not node.up:
  324. downlist += node.qualifiedname() + ' '
  325. elif node.timeout:
  326. timeoutlist += node.qualifiedname() + ' '
  327. elif not node.avail:
  328. unavlist += node.qualifiedname() + ' '
  329. elif node.isempty():
  330. emptylist += node.qualifiedname() + ' '
  331. nodes = [ node for node in nodes if node.up
  332. and not node.timeout
  333. and node.avail
  334. and not node.isempty() ]
  335. # Split notes by location
  336. lcm1nodes = [ node for node in nodes if node.location == 'LCM1' ]
  337. lcm2nodes = [ node for node in nodes if node.location == 'LCM2' ]
  338. laurnodes = [ node for node in nodes if node.location == 'LAUR' ]
  339. cudanodes = [ node for node in nodes if node.location == 'CUDA' ]
  340. # Print out
  341. if len(lcm1nodes):
  342. print splitters['LCM1']
  343. for node in lcm1nodes:
  344. node.printlist()
  345. if len(lcm2nodes):
  346. print splitters['LCM2']
  347. for node in lcm2nodes:
  348. node.printlist()
  349. if len(laurnodes):
  350. print splitters['LAUR']
  351. for node in laurnodes:
  352. node.printlist()
  353. if len(cudanodes):
  354. print splitters['CUDA']
  355. for node in cudanodes:
  356. node.printlist()
  357. # Some final output
  358. if progressbar > 0:
  359. print
  360. print 'Legenda: ' + green + 'utente locale '+ normal \
  361. + blue + 'utente remoto ' + normal + red + 'admin locale ' + normal \
  362. + pink + 'admin remoto ' + normal + turquoise + 'nirvana' + normal
  363. # FIXME I don't think we need this. Cuda nodes have their own section
  364. # print " I nodi contrassegnati con [C] sono i nodi CUDA."
  365. if args.math:
  366. print " I nodi contrassegnati con [M4] hanno Mathematica 4.0"
  367. print " I nodi contrassegnati con [M5] hanno Mathematica 5.2"
  368. print
  369. # Only in full mode
  370. if args.full or args.math or args.cuda:
  371. print red+'Empty: '+normal+emptylist
  372. print red+'Timeout: '+normal+timeoutlist
  373. print red+'Unreachable: '+normal+downlist
  374. print red+'Unavailable: '+normal+unavlist
  375. # Exit gracefully
  376. exit(0)