energystats.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. """
  2. Make energy available as a statistic by running a partial McPAT on every statistics snapshot save
  3. Works by registering a PRE_STAT_WRITE hook, which, before a stats snapshot write is triggered:
  4. - Writes the current statistics to the database using the energystats-temp prefix
  5. - Calls McPAT on the partial period (last-snapshot, energystats-temp)
  6. - Processes the McPAT results, making them available through custom-callback statistics
  7. - Finally the actual snapshot is written, including updated values for all energy counters
  8. """
  9. import sys, os, sim
  10. def build_dvfs_table(tech):
  11. # Build a table of (frequency, voltage) pairs.
  12. # Frequencies should be from high to low, and end with zero (or the lowest possible frequency)
  13. if tech == 22:
  14. return [ (2000, 1.0), (1800, 0.9), (1500, 0.8), (1000, 0.7), (0, 0.6) ]
  15. elif tech == 45:
  16. return [ (2000, 1.2), (1800, 1.1), (1500, 1.0), (1000, 0.9), (0, 0.8) ]
  17. else:
  18. raise ValueError('No DVFS table available for %d nm technology node' % tech)
  19. class Power:
  20. def __init__(self, static, dynamic):
  21. self.s = static
  22. self.d = dynamic
  23. def __add__(self, v):
  24. return Power(self.s + v.s, self.d + v.d)
  25. def __sub__(self, v):
  26. return Power(self.s - v.s, self.d - v.d)
  27. class EnergyStats:
  28. def setup(self, args):
  29. args = dict(enumerate((args or '').split(':')))
  30. interval_ns = long(args.get(0, None) or 1000000) # Default power update every 1 ms
  31. sim.util.Every(interval_ns * sim.util.Time.NS, self.periodic, roi_only = True)
  32. self.dvfs_table = build_dvfs_table(int(sim.config.get('power/technology_node')))
  33. #
  34. self.name_last = None
  35. self.time_last_power = 0
  36. self.time_last_energy = 0
  37. self.in_stats_write = False
  38. self.power = {}
  39. self.energy = {}
  40. for metric in ('energy-static', 'energy-dynamic'):
  41. for core in range(sim.config.ncores):
  42. sim.stats.register('core', core, metric, self.get_stat)
  43. sim.stats.register('L1-I', core, metric, self.get_stat)
  44. sim.stats.register('L1-D', core, metric, self.get_stat)
  45. sim.stats.register('L2', core, metric, self.get_stat)
  46. #sim.stats.register_per_thread('core-'+metric, 'core', metric)
  47. #sim.stats.register_per_thread('L1-I-'+metric, 'L1-I', metric)
  48. #sim.stats.register_per_thread('L1-D-'+metric, 'L1-D', metric)
  49. #sim.stats.register_per_thread('L2-'+metric, 'L2', metric)
  50. sim.stats.register('processor', 0, metric, self.get_stat)
  51. sim.stats.register('dram', 0, metric, self.get_stat)
  52. def periodic(self, time, time_delta):
  53. self.update()
  54. def hook_pre_stat_write(self, prefix):
  55. if not self.in_stats_write:
  56. self.update()
  57. def hook_sim_end(self):
  58. if self.name_last:
  59. sim.util.db_delete(self.name_last, True)
  60. def update(self):
  61. if sim.stats.time() == self.time_last_power:
  62. # Time did not advance: don't recompute
  63. return
  64. if not self.power or (sim.stats.time() - self.time_last_power >= 10 * sim.util.Time.US):
  65. # Time advanced significantly, or no power result yet: compute power
  66. # Save snapshot
  67. current = 'energystats-temp%s' % ('B' if self.name_last and self.name_last[-1] == 'A' else 'A')
  68. self.in_stats_write = True
  69. sim.stats.write(current)
  70. self.in_stats_write = False
  71. # If we also have a previous snapshot: update power
  72. if self.name_last:
  73. power = self.run_power(self.name_last, current)
  74. self.update_power(power)
  75. # Clean up previous last
  76. if self.name_last:
  77. sim.util.db_delete(self.name_last)
  78. # Update new last
  79. self.name_last = current
  80. self.time_last_power = sim.stats.time()
  81. # Increment energy
  82. self.update_energy()
  83. def get_stat(self, objectName, index, metricName):
  84. if not self.in_stats_write:
  85. self.update()
  86. return self.energy.get((objectName, index, metricName), 0L)
  87. def update_power(self, power):
  88. def get_power(component, prefix = ''):
  89. return Power(component[prefix + 'Subthreshold Leakage'] + component[prefix + 'Gate Leakage'], component[prefix + 'Runtime Dynamic'])
  90. for core in range(sim.config.ncores):
  91. self.power[('L1-I', core)] = get_power(power['Core'][core], 'Instruction Fetch Unit/Instruction Cache/')
  92. self.power[('L1-D', core)] = get_power(power['Core'][core], 'Load Store Unit/Data Cache/')
  93. self.power[('L2', core)] = get_power(power['Core'][core], 'L2/')
  94. self.power[('core', core)] = get_power(power['Core'][core]) - (self.power[('L1-I', core)] + self.power[('L1-D', core)] + self.power[('L2', core)])
  95. self.power[('processor', 0)] = get_power(power['Processor'])
  96. self.power[('dram', 0)] = get_power(power['DRAM'])
  97. def update_energy(self):
  98. if self.power and sim.stats.time() > self.time_last_energy:
  99. time_delta = sim.stats.time() - self.time_last_energy
  100. for (component, core), power in self.power.items():
  101. self.energy[(component, core, 'energy-static')] = self.energy.get((component, core, 'energy-static'), 0) + long(time_delta * power.s)
  102. self.energy[(component, core, 'energy-dynamic')] = self.energy.get((component, core, 'energy-dynamic'), 0) + long(time_delta * power.d)
  103. self.time_last_energy = sim.stats.time()
  104. def get_vdd_from_freq(self, f):
  105. # Assume self.dvfs_table is sorted from highest frequency to lowest
  106. for _f, _v in self.dvfs_table:
  107. if f >= _f:
  108. return _v
  109. assert ValueError('Could not find a Vdd for invalid frequency %f' % f)
  110. def gen_config(self, outputbase):
  111. freq = [ sim.dvfs.get_frequency(core) for core in range(sim.config.ncores) ]
  112. vdd = [ self.get_vdd_from_freq(f) for f in freq ]
  113. configfile = outputbase+'.cfg'
  114. cfg = open(configfile, 'w')
  115. cfg.write('''
  116. [perf_model/core]
  117. frequency[] = %s
  118. [power]
  119. vdd[] = %s
  120. ''' % (','.join(map(lambda f: '%f' % (f / 1000.), freq)), ','.join(map(str, vdd))))
  121. cfg.close()
  122. return configfile
  123. def run_power(self, name0, name1):
  124. outputbase = os.path.join(sim.config.output_dir, 'energystats-temp')
  125. configfile = self.gen_config(outputbase)
  126. os.system('unset PYTHONHOME; %s -d %s -o %s -c %s --partial=%s:%s --no-graph --no-text' % (
  127. os.path.join(os.getenv('SNIPER_ROOT'), 'tools/mcpat.py'),
  128. sim.config.output_dir,
  129. outputbase,
  130. configfile,
  131. name0, name1
  132. ))
  133. result = {}
  134. execfile(outputbase + '.py', {}, result)
  135. return result['power']
  136. # All scripts execute in global scope, so other scripts will be able to call energystats.update()
  137. energystats = EnergyStats()
  138. sim.util.register(energystats)