import os
settings_file_path = os.path.realpath(__file__)
settings_dir_path = os.path.dirname(settings_file_path)
os.sys.path.insert(0, settings_dir_path)
os.sys.path.insert(0, settings_dir_path + '/../../configs')
os.sys.path.insert(0, settings_dir_path + '/../../configs/sniper')
from configs.sims import Sniper_config
from sims.Sniper import simulate_benchmark
import gym
from gym.utils import seeding
from envHelpers import helpers
from loggers import write_csv
import numpy as np
import sys
import math
import subprocess
import time
import re
import numpy
import random
import json
import collections
[docs]
class SniperEnv(gym.Env):
def __init__(self):
self.action_space = gym.spaces.Discrete(128)
# Todo: Change the values if we normalize the observation space
self.observation_space = gym.spaces.Box(low=0, high=1e10, shape=(1,11))
self.steps = 0
self.max_steps = 1000
self.steps = 0
self.max_episode_len = 10
self.episode = 0
self.binary_name = Sniper_config.sniper_binary_name
self.binary_path = Sniper_config.sniper_binary_path
self.sniper_config = Sniper_config.sniper_config
self.sniper_workload = Sniper_config.spec_workload
# For batch mode, we will pass unique logdir for each agent
if(Sniper_config.sniper_mode == 'batch'):
self.output_dirs = []
self.agent_configs =[]
self.cummulative_reward = []
self.logdir = Sniper_config.sniper_logdir
else:
self.logdir = Sniper_config.sniper_logdir
self.cummulative_reward = 0
self.cores = Sniper_config.sniper_numcores
self.helpers = helpers()
#self.reset()
self.cummulative_reward = 0
[docs]
def step_multiagent(self, actions):
'''
1) Take actions from all agents.
2) Launch a batch jobs for each agent
3) Wait for all jobs to finish
4) return the observation, reward, done, info for each agent
'''
self.steps += 1
done = False
agent_write_ok = []
# create copies of the config files
for agent_ids in range(len(actions)):
self.agent_configs.append(self.helpers.create_agent_configs(
agent_ids,Sniper_config.sniper_config,
))
# check if all the config files are created
for each_config in self.agent_configs:
if not os.path.exists(each_config):
print("Error: Config file for agent {} does not exist.".format(each_config))
sys.exit()
else:
print("Config file for agent {} exists.".format(each_config))
# write the actions to the config files for each agent
for each_action in range(len(self.agent_configs)):
id = self.agent_configs[each_action].split("/")[-1].split(".")[0].split("_")[-1]
agent_id = "agent_" + id
agent_action = actions[agent_id]
agent_write_ok.append(self.actionToConfigs(agent_action,
self.agent_configs[each_action]))
# if configs are updated, then launch the sniper batch jobs
if (False in agent_write_ok):
print("Error: Not all the config files were updated")
sys.exit()
else:
print("Running Sniper Batch Jobs")
obs_dict = self.runSniperBatch(len(actions))
# convert obs dict to list of list
obs = []
for each_obs in obs_dict:
print(obs_dict[each_obs])
obs.append(self.dict_to_obs(obs_dict[each_obs]))
# Calculate the reward for each agent
rewards = []
for each_obs in obs:
rewards.append(self.calculate_reward(each_obs))
print(obs)
return obs, rewards, done, {}
[docs]
def reset_multiagent(self):
for each_config in self.agent_configs:
os.remove(each_config)
for each_output in self.output_dirs:
print("Deleting Old logs!:", each_output)
os.system("rm -rf {}".format(each_output))
# sleep for a while to make sure the files are deleted
time.sleep(60)
[docs]
def step(self, action):
self.steps += 1
done = False
status = self.actionToConfigs(action,self.sniper_config)
if(status):
obs = self.runSniper()
done = True
reward = self.calculate_reward(obs)
else:
print(f"{bcolors.FAIL}Warning: No active frommets remain. Continue?{bcolors.ENDC}")
# To do : Add some stopping conditions when dealing with real workload
return obs, reward, done, {}
[docs]
def reset(self):
print("Reseting Environment!")
self.steps = 0
self.cummulative_reward = 0
self.obs = self.observation_space.sample()
# Delete the logs to make sure every step is a new run
if os.path.exists(self.logdir):
print("Deleting Logs")
subprocess.call(["rm", "-rf", self.logdir])
return self.obs
[docs]
def actionToConfigs(self,action, cfg):
'''
Converts actions output from the agent to update the configuration files
'''
write_ok = self.helpers.read_modify_write_sniper_config(action,cfg)
# Todo: Maybe there is a cleaner way to write the config file
# workaround: add include to the config file each time we take a new action
# Sniper seems to need this: https://groups.google.com/g/snipersim/c/bXvBb6SXZ0k
dir_path = os.path.dirname(cfg)
if os.path.exists(cfg):
print("Adding include to config file")
process = subprocess.Popen(["sed", "-i",'1i #include nehalem',cfg], stdout=subprocess.PIPE
,stderr=subprocess.PIPE)
out, err = process.communicate()
print("Adding Include Nehalem")
process = subprocess.Popen(["sed", "-i",'1i #include rob',cfg], stdout=subprocess.PIPE
,stderr=subprocess.PIPE)
out, err = process.communicate()
return write_ok
[docs]
def runSniperBatch(self, num_agents):
'''
Runs the sniper in batch mode
'''
done = False
launcher = simulate_benchmark.SniperLauncher(int(self.cores))
jobs = [Sniper_config.spec_workload for _ in range(num_agents)]
results = []
for agent_idx in range(len(jobs)):
output_dir = 'agent_.{}_.{}'.format(agent_idx,jobs[agent_idx])
self.output_dirs.append(output_dir)
benchmark = jobs[agent_idx]
# The callback function is optional.
print("configs:", self.agent_configs[agent_idx])
result = launcher.batch_benchmark(benchmark, 'CPU2017', output_dir, self.agent_configs[agent_idx], callback=self.create_callback(output_dir))
results.append(result)
print('Launched Agent{}_{}'.format(agent_idx,benchmark))
for result in results:
result.wait()
for output_dir in self.output_dirs:
try:
# Now combine the stats.
simulate_benchmark.combine_stats(output_dir)
except Exception as e:
# If the output files are not found, Python will throw an exception. That can be gracefully handled here.
print(e)
obs = collections.defaultdict(dict)
for idx in range(len(self.output_dirs)):
basedir = os.path.join(Sniper_config.sniper_binary_path, self.output_dirs[idx])
# rety this operation till stats.json file is created
retry_count = 0
while(retry_count < 10):
if(os.path.exists(os.path.join(basedir, 'stats.json'))):
with open(os.path.join(basedir, 'stats.json')) as json_file:
data = json.load(json_file)
agent_name = "agent_" + str(idx)
obs[agent_name]['runtime'] = data['Time']
obs[agent_name]['branch_predictor_mpki'] = data["Branch Prediction"]["MPKI"]
obs[agent_name]['branch_mispredict_rate'] = data["Branch Prediction"]["misprediction rate"]
obs[agent_name]['l1_dcache_mpki'] = data["Cache"]["Cache L1-D"]["MPKI"]
obs[agent_name]['l1_dcache_missrate'] = data["Cache"]["Cache L1-D"]["miss rate"]
obs[agent_name]['l1_icache_mpki'] = data["Cache"]["Cache L1-I"]["MPKI"]
obs[agent_name]['l1_icache_missrate'] = data["Cache"]["Cache L1-I"]["miss rate"]
obs[agent_name]['l2_mpki'] = data["Cache"]["Cache L2"]["MPKI"]
obs[agent_name]['l2_missrate'] = data["Cache"]["Cache L2"]["miss rate"]
obs[agent_name]['l3_mpki'] = data["Cache"]["Cache L3"]["MPKI"]
obs[agent_name]['l3_missrate'] = data["Cache"]["Cache L3"]["miss rate"]
obs[agent_name]['power_dynamic'] = data["Power"]["Processor"]["Runtime Dynamic"]
obs[agent_name]['power_peak'] = data["Power"]["Processor"]["Peak Power"]
obs[agent_name]['area'] = data["Power"]["Processor"]["Area"]
break
else:
retry_count += 1
time.sleep(1)
print("stats.json file is not present, retry count: ", retry_count)
if(retry_count == 10):
# write a message to a file
with open(os.path.join(basedir, 'error.log'), 'w') as f:
f.write("stats.json file is not present! Shutting down!")
return obs
[docs]
def runSniper(self):
done = False
exe_path = self.binary_path
exe_name = self.binary_name
exe_final = os.path.join(exe_path,exe_name)
output_file = os.path.join(self.logdir,"stats.json")
cmd = exe_final
args = " -c" + " " + self.sniper_config + " -d " + self.logdir + " -n " + self.cores
process = subprocess.check_output(["python", cmd,
self.sniper_workload,
"-c", self.sniper_config,
"-d", self.logdir,
"-n", self.cores])
#process = subprocess.Popen(["python", cmd, args],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#process = subprocess.check_output(["python", cmd, args])#,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#process.wait()
#wait for the output file to be created and update done flag
if os.path.exists(output_file):
done = True
else:
done = False
data = {}
# read from a json file if done
if done:
with open(output_file) as json_file:
data = json.load(json_file)
else:
# To do : Gracefully hanfl ethis case
sys.exit("Error: Sniper did not finish")
# read relevant data from the json file
runtime = data["Time"]
branch_predictor_mpki = data["Branch Prediction"]["MPKI"]
branch_mispredict_rate = data["Branch Prediction"]["misprediction rate"]
l1_dcache_mpki = data["Cache"]["Cache L1-D"]["MPKI"]
l1_dcache_missrate = data["Cache"]["Cache L1-D"]["miss rate"]
l1_icache_mpki = data["Cache"]["Cache L1-I"]["MPKI"]
l1_icache_missrate = data["Cache"]["Cache L1-I"]["miss rate"]
l2_mpki = data["Cache"]["Cache L2"]["MPKI"]
l2_missrate = data["Cache"]["Cache L2"]["miss rate"]
l3_mpki = data["Cache"]["Cache L3"]["MPKI"]
l3_missrate = data["Cache"]["Cache L3"]["miss rate"]
obs = [runtime, branch_predictor_mpki, branch_mispredict_rate, l1_dcache_mpki,
l1_dcache_missrate, l1_icache_mpki, l1_icache_missrate, l2_mpki, l2_missrate,
l3_mpki, l3_missrate]
return obs
[docs]
def calculate_reward(self,obs):
'''
Calculates the reward based on the current observation
'''
reward = 0
latency = math.pow((obs[0] - Sniper_config.target_latency)/Sniper_config.target_latency,2)
power = math.pow((obs[1]- Sniper_config.target_power)/Sniper_config.target_power,2)
area = math.pow((obs[2]- Sniper_config.target_area)/Sniper_config.target_area,2)
reward = math.sqrt(latency + power + area)
return reward
[docs]
def create_callback(self, output_dir):
return lambda res : simulate_benchmark.error_check(output_dir)
[docs]
def dict_to_obs(self, obs_dict):
runtime = obs_dict["runtime"]
branch_predictor_mpki = obs_dict['branch_predictor_mpki']
branch_mispredict_rate = obs_dict['branch_mispredict_rate']
l1_dcache_mpki = obs_dict['l1_dcache_mpki']
l1_dcache_missrate = obs_dict['l1_dcache_missrate']
l1_icache_mpki = obs_dict['l1_icache_mpki']
l1_icache_missrate = obs_dict['l1_icache_missrate']
l2_mpki = obs_dict['l2_mpki']
l2_missrate = obs_dict['l2_missrate']
l3_mpki = obs_dict['l3_mpki']
l3_missrate = obs_dict['l3_missrate']
area = obs_dict['area']
power = obs_dict['power_dynamic']
return [runtime, power, area, branch_predictor_mpki, branch_mispredict_rate, l1_dcache_mpki,
l1_dcache_missrate, l1_icache_mpki, l1_icache_missrate, l2_mpki, l2_missrate,
l3_mpki, l3_missrate]