log_system_usage.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. #!/usr/bin/python3
  2. #
  3. import time
  4. import threading
  5. import subprocess
  6. import sys
  7. import pickle
  8. import gzip
  9. #
  10. PROC_STAT_HEADERS = ('user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq', 'steal', 'guest', 'guest_nice')
  11. #
  12. def get_cpu_stats(path='/proc/stat'):
  13. """
  14. Get CPU statistics from /proc/stat. Output is returned for the system and each CPU.
  15. Ex:
  16. {'system': {'user': 8696397, 'nice': 22431, ...},
  17. 'cpus': {
  18. 0: {'user': 4199206, 'nice': 11615, ...},
  19. 1: {'user': 4199308, 'nice': 10642, ...}
  20. }
  21. }
  22. """
  23. #
  24. with open(path, 'r') as f:
  25. lines = f.readlines()
  26. #
  27. cpu_lines = [l for l in lines if l.startswith('cpu')]
  28. stats = {'system':None, 'cpus':{}}
  29. #
  30. for l in cpu_lines:
  31. l_split = l.split()
  32. cpu_index = l_split[0][3:]
  33. cpu_stats = {x[0]: int(x[1]) for x in zip(PROC_STAT_HEADERS, l_split[1:])}
  34. #
  35. if cpu_index == '':
  36. stats['system'] = cpu_stats
  37. else:
  38. stats['cpus'][int(cpu_index)] = cpu_stats
  39. #
  40. #
  41. return stats
  42. #
  43. def calculate_cpu_usage(initial, current):
  44. """
  45. Calculation adapted from: https://stackoverflow.com/questions/23367857/accurate-calculation-of-cpu-usage-given-in-percentage-in-linux/
  46. """
  47. #
  48. initial_idle = initial['idle'] + initial['iowait']
  49. current_idle = current['idle'] + current['iowait']
  50. #
  51. initial_non_idle = initial['user'] + initial['nice'] + initial['system'] + initial['irq'] + initial['softirq'] + initial['steal']
  52. current_non_idle = current['user'] + current['nice'] + current['system'] + current['irq'] + current['softirq'] + current['steal']
  53. #
  54. initial_total = initial_idle + initial_non_idle
  55. current_total = current_idle + current_non_idle
  56. #
  57. return (current_non_idle-initial_non_idle)/(current_total-initial_total)
  58. #
  59. def calculate_cpu_usage_continuous(stats):
  60. cpu_usages = []
  61. for i in range(len(stats)-1):
  62. cpu_usages.append(calculate_cpu_usage(stats[i], stats[i+1]))
  63. #
  64. return cpu_usages
  65. #
  66. def get_running_processes():
  67. lines = subprocess.check_output(['ps', '-a', '-x', '-o', 'pid,state,args', '--no-headers']).decode('utf-8').split('\n')
  68. lines = [line.strip() for line in lines]
  69. lines = [line.split(' ', 2) for line in lines if len(line) != 0]
  70. #
  71. data = []
  72. for line in lines:
  73. data.append({'pid':int(line[0]), 'state':line[1], 'args':line[2]})
  74. #
  75. return data
  76. #
  77. def log_cpu_stats(path, interval, stop_event):
  78. """
  79. Log the cpu stats to a gz compressed JSON file. Storing JSON
  80. seems to only use about 10% more disk space than storing
  81. bytes directly (4 bytes per value), so JSON is used for
  82. simplicity.
  83. path: file to save to
  84. interval: how many seconds to wait before getting more data
  85. stop_event: a threading.Event which stops the function
  86. """
  87. #
  88. stats = {'timestamps':[], 'stats':{'system':[], 'cpus':{}}, 'processes':[]}
  89. while not stop_event.is_set():
  90. stats['timestamps'].append(time.time())
  91. #stats['stats'].append(get_cpu_stats())
  92. current_stats = get_cpu_stats()
  93. stats['stats']['system'].append(current_stats['system'])
  94. for cpu in current_stats['cpus']:
  95. if cpu not in stats['stats']['cpus']:
  96. stats['stats']['cpus'][cpu] = []
  97. #
  98. stats['stats']['cpus'][cpu].append(current_stats['cpus'][cpu])
  99. #
  100. stats['processes'].append(get_running_processes())
  101. stop_event.wait(interval)
  102. #
  103. with gzip.GzipFile(path, 'wb') as f:
  104. pickle.dump(stats, f, protocol=4)
  105. #
  106. #
  107. def load_cpu_stats(path):
  108. with gzip.GzipFile(path, 'rb') as f:
  109. return pickle.load(f)
  110. #
  111. #
  112. '''
  113. def log_cpu_stats(path, interval, stop_event):
  114. path: file to save to
  115. interval: how many seconds to wait before getting more data
  116. stop_event: a threading.Event which stops the function
  117. #
  118. with gzip.GzipFile(path+'.2.gz', 'w') as f:
  119. f.write(' '.join(PROC_STAT_HEADERS).encode('utf-8'))
  120. f.write('\n\n'.encode('utf-8'))
  121. #
  122. while not stop_event.is_set():
  123. f.write(str(time.time()).encode('utf-8'))
  124. f.write('\n'.encode('utf-8'))
  125. stats = get_cpu_stats()
  126. f.write('cpu '.encode('utf-8'))
  127. #f.write(' '.join([str(stats['system'][x]) for x in PROC_STAT_HEADERS]).encode('utf-8'))
  128. f.write(b''.join([stats['system'][x].to_bytes(4, byteorder='big') for x in PROC_STAT_HEADERS]))
  129. f.write('\n'.encode('utf-8'))
  130. for cpu in stats['cpus']:
  131. f.write('cpu{} '.format(cpu).encode('utf-8'))
  132. #f.write(' '.join([str(stats['cpus'][cpu][x]) for x in PROC_STAT_HEADERS]).encode('utf-8'))
  133. f.write(b''.join([stats['cpus'][cpu][x].to_bytes(4, byteorder='big') for x in PROC_STAT_HEADERS]))
  134. f.write('\n'.encode('utf-8'))
  135. #
  136. f.write('\n'.encode('utf-8'))
  137. time.sleep(interval)
  138. #
  139. #
  140. #
  141. '''
  142. if __name__ == '__main__':
  143. stop_event = threading.Event()
  144. #
  145. assert len(sys.argv) == 3
  146. interval = float(sys.argv[1])
  147. file_name = sys.argv[2]
  148. #
  149. t = threading.Thread(target=log_cpu_stats, args=(file_name, interval, stop_event))
  150. t.start()
  151. #
  152. try:
  153. while True:
  154. time.sleep(100)
  155. #
  156. except KeyboardInterrupt:
  157. stop_event.set()
  158. print()
  159. #
  160. t.join()
  161. #