log_system_usage.py 4.2 KB

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