whoall 14 KB

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