#!/bin/bash

# This script runs 'perf stat' on a specified program 10 times,
# calculates the average of key performance metrics, and computes
# IPC and branch miss percentage. The results are saved to a JSON file.

#  'perf stat' 10 
#  IPC 
#  JSON 

exe_name=$1
# Check if a program path is provided as a command-line argument.
if [ -z "$1" ]; then
    exe_name=$1
    echo "Error: Please provide the path to the executable file." >&2
    echo "" >&2
    echo "Usage: sudo ./perf_avg_analyzer.sh <path/to/your/program> <params>" >&2
    exit 1
fi

OUTPUT_DIR=./
# Get the directory and size of the executable to store the output file.
OUTPUT_DIR=$(dirname $(dirname "$exe_name"))
#OUTPUT_DIR=perf_jsons
PROGRAM_NAME=$(basename "$exe_name" .out)
OUTPUT_FILE="${OUTPUT_DIR}/perf_jsons/perf_stats_avg_${PROGRAM_NAME}.json"
file_size=$(stat -c %s "$exe_name")

if [[ "$2" == "needoutput" ]]; then
    outputflag=" > $OUTPUT_DIR/tmp_out"
    shift 1
fi

if [[ "$2" == "needinput" ]]; then
    outputflag=" > $OUTPUT_DIR/tmp_out"
    inputflag=" < $3"
    shift 2
fi

shift 1
params=$@

#eval "$params"

# Check if jq is installed. jq is required for JSON formatting.
if ! command -v jq &> /dev/null
then
    echo "Error: 'jq' is not installed. Please install it to run this script." >&2
    echo " 'jq' " >&2
    exit 1
fi

# Initialize sum variables for each metric.
# 
total_cycles=0
total_instructions=0
total_uops_issued=0
total_l1_miss=0
total_l2_miss=0
total_l3_miss=0
total_stalls_mem=0
total_branches=0
total_branch_misses=0
total_elapsed_time=0
total_user_time=0
total_sys_time=0

NUM_RUNS=20

echo "Running 'perf stat' $NUM_RUNS times on '$exe_name' $params $inputflag $outputflag..."
echo " '$exe_name $params'  'perf stat' $NUM_RUNS ..."

benchpath=$(dirname $(dirname $exe_name))
cd $benchpath

WARMUP_RUNS=5
for i in $(seq 1 $WARMUP_RUNS); do
    /bin/bash -c "taskset -c 0 \"$exe_name\" $params $inputflag  $outputflag"
done

# Loop to run the command multiple times and accumulate values.
for i in $(seq 1 $NUM_RUNS); do
    echo "Run $i of $NUM_RUNS..."
    TARGET_CMD="taskset -c 0 \"$exe_name\" $params $inputflag $outputflag"
    PERF_OUTPUT=$(perf stat -e cycles,instructions,uops_issued.any,mem_load_retired.l1_miss,mem_load_retired.l2_miss,mem_load_retired.l3_miss,cycle_activity.stalls_mem_any,branches,branch-misses -- /bin/bash -c "$TARGET_CMD" 2>&1)
    echo $PERF_OUTPUT

    # Extract and accumulate values for each metric.
    # 
    cycles=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+cycles' | awk '{print $1}' | sed 's/,//g')
    instructions=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+instructions' | awk '{print $1}' | sed 's/,//g')
    uops_issued=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+uops_issued\.any' | awk '{print $1}' | sed 's/,//g')
    l1_miss=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+mem_load_retired\.l1_miss' | awk '{print $1}' | sed 's/,//g')
    l2_miss=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+mem_load_retired\.l2_miss' | awk '{print $1}' | sed 's/,//g')
    l3_miss=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+mem_load_retired\.l3_miss' | awk '{print $1}' | sed 's/,//g')
    stalls_mem=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+cycle_activity\.stalls_mem_any' | awk '{print $1}' | sed 's/,//g')
    branches=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+branches' | awk '{print $1}' | sed 's/,//g')
    branch_misses=$(echo "$PERF_OUTPUT" | grep -E '^[[:space:]]*[0-9,]+[[:space:]]+branch-misses' | awk '{print $1}' | sed 's/,//g')
    elapsed_time=$(echo "$PERF_OUTPUT" | grep "seconds time elapsed" | awk '{print $1}')
    user_time=$(echo "$PERF_OUTPUT" | grep "seconds user" | awk '{print $1}')
    sys_time=$(echo "$PERF_OUTPUT" | grep "seconds sys" | awk '{print $1}')

    total_cycles=$(echo "$total_cycles + $cycles" | bc)
    total_instructions=$(echo "$total_instructions + $instructions" | bc)
    total_uops_issued=$(echo "$total_uops_issued + $uops_issued" | bc)
    total_l1_miss=$(echo "$total_l1_miss + $l1_miss" | bc)
    total_l2_miss=$(echo "$total_l2_miss + $l2_miss" | bc)
    total_l3_miss=$(echo "$total_l3_miss + $l3_miss" | bc)
    total_stalls_mem=$(echo "$total_stalls_mem + $stalls_mem" | bc)
    total_branches=$(echo "$total_branches + $branches" | bc)
    total_branch_misses=$(echo "$total_branch_misses + $branch_misses" | bc)
    total_elapsed_time=$(echo "$total_elapsed_time + $elapsed_time" | bc)
    total_user_time=$(echo "$total_user_time + $user_time" | bc)
    total_sys_time=$(echo "$total_sys_time + $sys_time" | bc)

    #./clean.sh
done

echo "Calculating averages..."
echo "..."

# Calculate the averages.
avg_cycles=$(echo "scale=4; $total_cycles / $NUM_RUNS" | bc)
avg_instructions=$(echo "scale=4; $total_instructions / $NUM_RUNS" | bc)
avg_uops_issued=$(echo "scale=4; $total_uops_issued / $NUM_RUNS" | bc)
avg_l1_miss=$(echo "scale=4; $total_l1_miss / $NUM_RUNS" | bc)
avg_l2_miss=$(echo "scale=4; $total_l2_miss / $NUM_RUNS" | bc)
avg_l3_miss=$(echo "scale=4; $total_l3_miss / $NUM_RUNS" | bc)
avg_stalls_mem=$(echo "scale=4; $total_stalls_mem / $NUM_RUNS" | bc)
avg_branches=$(echo "scale=4; $total_branches / $NUM_RUNS" | bc)
avg_branch_misses=$(echo "scale=4; $total_branch_misses / $NUM_RUNS" | bc)
avg_elapsed_time=$(echo "scale=4; $total_elapsed_time / $NUM_RUNS" | bc)
avg_user_time=$(echo "scale=4; $total_user_time / $NUM_RUNS" | bc)
avg_sys_time=$(echo "scale=4; $total_sys_time / $NUM_RUNS" | bc)

# Calculate IPC and branch miss percentage.
#  IPC 
ipc=$(echo "scale=4; $avg_instructions / $avg_cycles" | bc)
br_misp=$(echo "scale=4; ($avg_branch_misses / $avg_branches) * 100" | bc)

# Use jq to build the JSON object from the calculated averages and other values.
#  jq  JSON 
jq -n \
  --arg file_size "$file_size" \
  --arg cycles "$avg_cycles" \
  --arg instructions "$avg_instructions" \
  --arg uops "$avg_uops_issued" \
  --arg l1_miss "$avg_l1_miss" \
  --arg l2_miss "$avg_l2_miss" \
  --arg l3_miss "$avg_l3_miss" \
  --arg stalls "$avg_stalls_mem" \
  --arg branches "$avg_branches" \
  --arg branch_misses "$avg_branch_misses" \
  --arg elapsed_time "$avg_elapsed_time" \
  --arg user_time "$avg_user_time" \
  --arg sys_time "$avg_sys_time" \
  --arg ipc "$ipc" \
  --arg br_misp "$br_misp" \
  '{
    "file_size_bytes": ($file_size | tonumber),
    "average_cycles": ($cycles | tonumber),
    "average_instructions": ($instructions | tonumber),
    "average_uops_issued_any": ($uops | tonumber),
    "average_mem_load_retired_l1_miss": ($l1_miss | tonumber),
    "average_mem_load_retired_l2_miss": ($l2_miss | tonumber),
    "average_mem_load_retired_l3_miss": ($l3_miss | tonumber),
    "average_cycle_activity_stalls_mem_any": ($stalls | tonumber),
    "average_branches": ($branches | tonumber),
    "average_branch_misses": ($branch_misses | tonumber),
    "average_time_elapsed": ($elapsed_time | tonumber),
    "average_time_user": ($user_time | tonumber),
    "average_time_sys": ($sys_time | tonumber),
    "IPC": ($ipc | tonumber),
    "BR_MISP": ($br_misp | tonumber)
  }' > "$OUTPUT_FILE"

# Print the generated JSON to the console for easy viewing.
cat "$OUTPUT_FILE"

echo -e "\nSuccessfully extracted and averaged perf stats over $NUM_RUNS runs."
echo "Results saved to $OUTPUT_FILE."
echo -e "\n $NUM_RUNS  perf "
echo " $OUTPUT_FILE"
