monitor.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. """
  2. This program is designed to inspect an application environment
  3. This program should only be run on unix friendly systems
  4. We enable the engines to be able to run a several configurations
  5. Similarly to what a visitor design-pattern would do
  6. """
  7. from __future__ import division
  8. import os
  9. import subprocess
  10. from sets import Set
  11. import re
  12. import datetime
  13. import urllib2 as http, base64
  14. from threading import Thread, RLock
  15. import time
  16. class Analysis:
  17. def __init__(self):
  18. self.logs = []
  19. pass
  20. def post(self,object):
  21. self.logs.append(object)
  22. def init(self):
  23. d = datetime.datetime.now()
  24. self.now = {"month":d.month,"year":d.year, "day":d.day,"hour":d.hour,"minute":d.minute}
  25. def getNow(self):
  26. d = datetime.datetime.now()
  27. return {"month":d.month,"year":d.year, "day":d.day,"hour":d.hour,"minute":d.minute}
  28. """
  29. This class is designed to analyze environment variables. Environment variables can either be folders, files or simple values
  30. The class returns a quantifiable assessment of the environment variables (expected 100%)
  31. """
  32. class Env(Analysis):
  33. def __init__(self):
  34. Analysis.__init__(self)
  35. def init(self,values):
  36. #Analysis.init(self)
  37. self.values = values
  38. """
  39. This function evaluate the validity of an environment variable by returning a 1 or 0 (computable)
  40. The function will use propositional logic (https://en.wikipedia.org/wiki/Propositional_calculus)
  41. """
  42. def evaluate(self,id):
  43. if id in os.environ :
  44. #
  45. # We can inspect to make sure the environment variable is not a path or filename.
  46. # Using propositional logic we proceed as follows:
  47. # - (p) We determine if the value is an folder or file name (using regex)
  48. # - (q) In case of a file or folder we check for existance
  49. # The final result is a conjuction of p and q
  50. #
  51. value = os.environ[id]
  52. expressions = [os.sep,'(\\.\w+)$']
  53. p = sum([ re.search(xchar,value) is not None for xchar in expressions])
  54. q = os.path.exists(value)
  55. return int(p and q)
  56. else:
  57. return 0
  58. def composite (self):
  59. #Analysis.init(self)
  60. r = [ self.evaluate(id) for id in self.values] ;
  61. N = len(r)
  62. n = sum(r)
  63. value = 100 * round(n/N,2)
  64. missing = [self.values[i] for i in range(0,N) if r[i] == 0]
  65. return dict(self.getNow(),**{"value":value,"missing":missing})
  66. """
  67. This class is designed to handle analaysis of the a python virtual environment i.e deltas between requirments file and a virtualenv
  68. @TODO: update the virtual environment
  69. """
  70. class Sandbox(Analysis):
  71. def __init__(self):
  72. Analysis.__init__(self)
  73. def init(self,conf):
  74. #Analysis.init(self)
  75. self.sandbox_path = conf['sandbox']
  76. self.requirements_path = conf['requirements']
  77. def get_requirements (self):
  78. f = open(self.requirements_path)
  79. return [ name.replace('-',' ').replace('_',' ') for name in f.read().split('\n') if name != '']
  80. """
  81. This function will return the modules installed in the sandbox (virtual environment)
  82. """
  83. def get_sandbox_requirements(self):
  84. cmd = ['freeze']
  85. xchar = ''.join([os.sep]*2)
  86. pip_vm = ''.join([self.sandbox_path,os.sep,'bin',os.sep,'pip']).replace(xchar,os.sep)
  87. cmd = [pip_vm]+cmd
  88. r = subprocess.check_output(cmd).split('\n')
  89. return [row.replace('-',' ').replace('_',' ') for row in r if row.strip() != '']
  90. def evaluate(self):
  91. pass
  92. """
  93. This function returns the ratio of existing modules relative to the ones expected
  94. """
  95. def composite(self):
  96. Analysis.init(self)
  97. required_modules= self.get_requirements()
  98. sandbox_modules = self.get_sandbox_requirements()
  99. N = len(required_modules)
  100. n = len(Set(required_modules) - Set(sandbox_modules))
  101. value = round(1 - (n/N),2)*100
  102. missing = list(Set(required_modules) - Set(sandbox_modules))
  103. return dict(self.getNow(),**{"value":value,"missing":missing})
  104. """
  105. This class performs the analysis of a list of processes and determines
  106. The class provides a quantifiable measure of how many processes it found over all
  107. """
  108. class ProcessCounter(Analysis):
  109. def __init__(self):
  110. Analysis.__init__(self)
  111. def init(self,names):
  112. #Analysis.init(self)
  113. self.names = names
  114. def evaluate(self,name):
  115. cmd = "".join(['ps -eo comm |grep ',name,' |wc -l'])
  116. handler = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
  117. return int(handler.communicate()[0].replace("\n","") )
  118. def composite(self):
  119. #Analysis.init(self)
  120. r = {}
  121. for name in self.names :
  122. r[name] = self.evaluate(name)
  123. #N = len(r)
  124. #n = sum(r)
  125. #return n/N
  126. return dict(self.getNow(),**r)
  127. """
  128. This class returns an application's both memory and cpu usage
  129. """
  130. class DetailProcess(Analysis):
  131. def __init__(self):
  132. Analysis.__init__(self)
  133. def init (self,names):
  134. #Analysis.init(self)
  135. self.names = names;
  136. def split(self,name,stream):
  137. pattern = "(\d+.{0,1}\d*)\x20*(\d+.{0,1}\d*)\x20*(\d+.{0,1}\d*)".replace(":name",name).strip()
  138. g = re.match(pattern,stream.strip())
  139. if g :
  140. return list(g.groups())+['1']+[name]
  141. else:
  142. return ''
  143. def evaluate(self,name) :
  144. cmd = "ps -eo pmem,pcpu,vsize,command|grep -E \":app\""
  145. handler = subprocess.Popen(cmd.replace(":app",name),shell=True,stdout=subprocess.PIPE)
  146. ostream = handler.communicate()[0].split('\n')
  147. #xstr = ostream
  148. ostream = [ self.split(name,row) for row in ostream if row != '' and 'grep' not in row]
  149. if len(ostream) == 0 or len(ostream[0]) < 4 :
  150. ostream = [['0','0','0','0',name]]
  151. r = []
  152. for row in ostream :
  153. #
  154. # Though the comm should only return the name as specified,
  155. # On OSX it has been observed that the fully qualified path is sometimes returned (go figure)
  156. #
  157. row = [float(value) for value in row if value.strip() != '' and name not in value ] +[re.sub('\$|^','',name)]
  158. r.append(row)
  159. #
  160. # At this point we should aggregate results
  161. # The aggregation is intended for applications with several processes (e.g: apache2)
  162. #
  163. if len(r) > 1:
  164. m = None
  165. for row in r:
  166. if m is None:
  167. m = row
  168. else:
  169. m[3] += row[3]
  170. m[0] += row[0]
  171. m[1] += row[1]
  172. m[2] += row[2]
  173. m[0] = round((m[0] / m[3]),2)
  174. m[1] = round((m[1] / m[3]),2)
  175. m[2] = round((m[2] / m[3]),2)
  176. r = [m]
  177. return r
  178. def status(self,row):
  179. x = row['memory_usage']
  180. y = row['cpu_usage']
  181. z = row['memory_available']
  182. if z :
  183. if y :
  184. return "running"
  185. return "idle"
  186. else:
  187. return "crash"
  188. def format(self,row):
  189. r= {"memory_usage":row[0],"cpu_usage":row[1],"memory_available":row[2]/1000,"proc_count":row[3],"label":row[4]}
  190. status = self.status(r)
  191. r['status'] = status
  192. return r
  193. def composite(self):
  194. ma = []
  195. now = self.getNow()
  196. for name in self.names:
  197. matrix = self.evaluate(name)
  198. ma += [ dict(now, **self.format(row)) for row in matrix]
  199. return ma
  200. class FileWatch(Analysis):
  201. def __init__(self,conf):
  202. pass
  203. def split(self,row):
  204. x = row.split(' ')
  205. r = {}
  206. months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
  207. if x:
  208. if 'K' in x[0]:
  209. print x
  210. size = float(x[0].replace('K','').replace('KB','')) / 1000
  211. elif 'M' in x[0] :
  212. size = x[0].replace('MB','')
  213. elif 'G' in x[0] :
  214. size = x[0].replace('GB','') * 1000
  215. elif 'T' in x[0] :
  216. pass
  217. month = months.index(x[1]) + 1
  218. day = x[2]
  219. print [' ** ',x[4]]
  220. #hour,minute = x[3].split(':')
  221. year = x[4]
  222. return {"size":size,"age":age}
  223. return None
  224. def evaluate(self,path):
  225. cmd = "find :path|xargs ls -lh |awk '{print $5,$6,$7,$8,$9}'".replace(":path",path)
  226. print cmd
  227. handler = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
  228. ostream = handler.communicate()[0].split('\n')
  229. print [self.split(stream) for stream in ostream if stream.strip() != '']
  230. pass
  231. class Monitor (Thread):
  232. def __init__(self,pConfig,pWriter,id='processes') :
  233. Thread.__init__(self)
  234. self.config = pConfig[id]
  235. self.writer = pWriter;
  236. self.logs = []
  237. self.handler = self.config['class']
  238. self.mconfig = self.config['config']
  239. def stop(self):
  240. self.keep_running = False
  241. def run(self):
  242. r = {}
  243. self.keep_running = True
  244. lock = RLock()
  245. while self.keep_running:
  246. lock.acquire()
  247. for label in self.mconfig:
  248. self.handler.init(self.mconfig[label])
  249. r = self.handler.composite()
  250. self.writer.write(label=label,row = r)
  251. time.sleep(2)
  252. lock.release()
  253. self.prune()
  254. TIME_LAPSE = 60*2
  255. time.sleep(TIME_LAPSE)
  256. print "Stopped ..."
  257. def prune(self) :
  258. MAX_ENTRIES = 100
  259. if len(self.logs) > MAX_ENTRIES :
  260. BEG = len(self.logs) - MAX_SIZE -1
  261. self.logs = self.logs[BEG:]