Add timeline visualization and fix resource profiling
- Save timeline data for each run (time, memory, CPU) - Create timelines directory structure - Add Python visualization script for charts - Fix integer comparison errors in profiling - Collect samples throughout program lifetime
This commit is contained in:
+30
-37
@@ -123,33 +123,38 @@ run_program() {
|
|||||||
local result
|
local result
|
||||||
local peak_memory=0
|
local peak_memory=0
|
||||||
local peak_cpu=0
|
local peak_cpu=0
|
||||||
|
local timeline_dir="timelines/$name"
|
||||||
|
|
||||||
|
# Create timeline directory
|
||||||
|
mkdir -p "$timeline_dir"
|
||||||
|
|
||||||
# Run 4 times, discard first run (warmup)
|
# Run 4 times, discard first run (warmup)
|
||||||
for i in 1 2 3 4; do
|
for i in 1 2 3 4; do
|
||||||
local mem_profile_file="/tmp/${name}_mem_$$_$i"
|
local timeline_file="$timeline_dir/run_$i.tsv"
|
||||||
local cpu_profile_file="/tmp/${name}_cpu_$$_$i"
|
|
||||||
local start=$(date +%s%N)
|
local start=$(date +%s%N)
|
||||||
|
|
||||||
# Run program and capture PID for resource profiling
|
# Run program and capture PID for resource profiling
|
||||||
if [ "$i" -gt 1 ]; then
|
"$@" 2>/dev/null &
|
||||||
# For runs 2-4, profile memory and CPU
|
local pid=$!
|
||||||
"$@" 2>/dev/null &
|
|
||||||
local pid=$!
|
# Profile resources in background
|
||||||
|
local resources=$(profile_resources "$pid" "/dev/null" "/dev/null" "$timeline_file")
|
||||||
|
local peak_mem=$(echo "$resources" | awk '{print $1}')
|
||||||
|
local peak_cpu_val=$(echo "$resources" | awk '{print $2}')
|
||||||
|
|
||||||
|
# Wait for process to complete
|
||||||
|
wait "$pid" 2>/dev/null
|
||||||
|
local exit_code=$?
|
||||||
|
|
||||||
|
local end=$(date +%s%N)
|
||||||
|
local elapsed=$(( (end - start) / 1000000 ))
|
||||||
|
|
||||||
|
if [ $exit_code -eq 0 ]; then
|
||||||
|
# Get result for verification
|
||||||
|
result=$("$@" 2>/dev/null)
|
||||||
|
|
||||||
# Profile resources in background
|
if [ "$i" -gt 1 ]; then
|
||||||
local resources=$(profile_resources "$pid" "$mem_profile_file" "$cpu_profile_file")
|
# Only count runs 2-4 for averages
|
||||||
local peak_mem=$(echo "$resources" | awk '{print $1}')
|
|
||||||
local peak_cpu_val=$(echo "$resources" | awk '{print $2}')
|
|
||||||
|
|
||||||
# Wait for process to complete
|
|
||||||
wait "$pid" 2>/dev/null
|
|
||||||
local exit_code=$?
|
|
||||||
|
|
||||||
if [ $exit_code -eq 0 ]; then
|
|
||||||
result=$("$@" 2>/dev/null)
|
|
||||||
local end=$(date +%s%N)
|
|
||||||
local elapsed=$(( (end - start) / 1000000 ))
|
|
||||||
|
|
||||||
total_time=$((total_time + elapsed))
|
total_time=$((total_time + elapsed))
|
||||||
total_memory=$((total_memory + peak_mem))
|
total_memory=$((total_memory + peak_mem))
|
||||||
total_cpu=$((total_cpu + peak_cpu_val))
|
total_cpu=$((total_cpu + peak_cpu_val))
|
||||||
@@ -164,23 +169,11 @@ run_program() {
|
|||||||
if verify "$result" "$DECIMALS"; then
|
if verify "$result" "$DECIMALS"; then
|
||||||
((success_count++))
|
((success_count++))
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
echo -e "${RED}ERROR${NC}"
|
|
||||||
results+=("999999 $name ERROR")
|
|
||||||
rm -f "$mem_profile_file" "$cpu_profile_file"
|
|
||||||
return
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$mem_profile_file" "$cpu_profile_file"
|
|
||||||
else
|
else
|
||||||
# First run (warmup) - just execute without profiling
|
echo -e "${RED}ERROR${NC}"
|
||||||
if result=$("$@" 2>/dev/null); then
|
results+=("999999 $name ERROR")
|
||||||
:
|
return
|
||||||
else
|
|
||||||
echo -e "${RED}ERROR${NC}"
|
|
||||||
results+=("999999 $name ERROR")
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -194,10 +187,10 @@ run_program() {
|
|||||||
local peak_memory_mb=$((peak_memory / 1024))
|
local peak_memory_mb=$((peak_memory / 1024))
|
||||||
|
|
||||||
if [ $success_count -eq 3 ]; then
|
if [ $success_count -eq 3 ]; then
|
||||||
echo -e "${GREEN}SUCCESS${NC} $avg_time ms, ${BLUE}${avg_memory_mb}MB mem, ${YELLOW}${avg_cpu}% CPU avg, ${peak_cpu}% CPU peak${NC}"
|
echo -e "${GREEN}SUCCESS${NC} $avg_time ms, ${BLUE}${avg_memory_mb}MB avg / ${peak_memory_mb}MB peak, ${YELLOW}${avg_cpu}% CPU avg / ${peak_cpu}% CPU peak${NC}"
|
||||||
results+=("$avg_time $name SUCCESS $avg_memory $peak_memory $avg_cpu $peak_cpu")
|
results+=("$avg_time $name SUCCESS $avg_memory $peak_memory $avg_cpu $peak_cpu")
|
||||||
else
|
else
|
||||||
echo -e "${RED}FAILED${NC} $avg_time ms, ${BLUE}${avg_memory_mb}MB mem, ${YELLOW}${avg_cpu}% CPU avg, ${peak_cpu}% CPU peak${NC}"
|
echo -e "${RED}FAILED${NC} $avg_time ms, ${BLUE}${avg_memory_mb}MB avg / ${peak_memory_mb}MB peak, ${YELLOW}${avg_cpu}% CPU avg / ${peak_cpu}% CPU peak${NC}"
|
||||||
results+=("$avg_time $name FAILED $avg_memory $peak_memory $avg_cpu $peak_cpu")
|
results+=("$avg_time $name FAILED $avg_memory $peak_memory $avg_cpu $peak_cpu")
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
13 2000 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
11 2000 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
16 2000 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
11 2000 0
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
11 2000 0
|
||||||
|
35 2016 0
|
||||||
|
59 2016 0
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
8 2000 0
|
||||||
|
29 0 0
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
9 2000 0
|
||||||
|
34 2000
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
8 2000 0
|
||||||
|
30 2000 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
11 240
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
7 0 0
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
13 2032 0
|
||||||
|
40 2032 0
|
||||||
|
64 2032 0
|
||||||
|
90 2032 0
|
||||||
|
116 2032 0
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
9 2016 0
|
||||||
|
33 2016 0
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
8 2032 0
|
||||||
|
33 2032 0
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
8 2000 0
|
||||||
|
33 2016 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
12 2288
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
6 0 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
6 0 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
5 0 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
10 464 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
8 496
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
8 496
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
8 496
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
12 5344 0
|
||||||
|
50 0 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
12 9088 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
13 10624 0
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
12 10016 0
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
15 2032 0
|
||||||
|
42 2032 0
|
||||||
|
65 2032 0
|
||||||
|
94 2032 0
|
||||||
|
119 2032 0
|
||||||
|
146 2032 0
|
||||||
|
169 2032 0
|
||||||
|
193 2032 0
|
||||||
|
221 2032 0
|
||||||
|
246 2032 0
|
||||||
|
270 2032 0
|
||||||
|
296 2032 0
|
||||||
|
319 2032 0
|
||||||
|
343 2032 0
|
||||||
|
367 2032 0
|
||||||
|
393 2032 0
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
9 2032 0
|
||||||
|
34 2032 0
|
||||||
|
62 2032 0
|
||||||
|
85 2032 0
|
||||||
|
109 2032 0
|
||||||
|
135 2032 0
|
||||||
|
158 2032 0
|
||||||
|
181 2032 0
|
||||||
|
213 2032 0
|
||||||
|
238 2032 0
|
||||||
|
262 2032 0
|
||||||
|
288 2032 0
|
||||||
|
311 2032 0
|
||||||
|
336 2032 0
|
||||||
|
359 2032 0
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
9 2032 0
|
||||||
|
31 2032 0
|
||||||
|
57 2032 0
|
||||||
|
79 2032 0
|
||||||
|
102 2032 0
|
||||||
|
126 2032 0
|
||||||
|
155 2032 0
|
||||||
|
179 2032 0
|
||||||
|
202 2032 0
|
||||||
|
227 2032 0
|
||||||
|
249 2032 0
|
||||||
|
272 2032 0
|
||||||
|
296 2032 0
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
8 2048 0
|
||||||
|
31 2048 0
|
||||||
|
57 2048 0
|
||||||
|
82 2048 0
|
||||||
|
106 2048 0
|
||||||
|
130 2048 0
|
||||||
|
155 2048 0
|
||||||
|
179 2048 0
|
||||||
|
206 2048 0
|
||||||
|
230 2048 0
|
||||||
|
256 2048 0
|
||||||
|
279 2048 0
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
12 2048 0
|
||||||
|
36 2048 0
|
||||||
|
60 2048 0
|
||||||
|
84 2048 0
|
||||||
|
108 2048 0
|
||||||
|
132 0 0
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
8 2032 0
|
||||||
|
33 2032 0
|
||||||
|
56 2032 0
|
||||||
|
79 2032 0
|
||||||
|
102 2032 0
|
||||||
|
128 2032 0
|
||||||
|
151 2032 0
|
||||||
|
177 2032 0
|
||||||
|
206 2032 0
|
||||||
|
227 2032 0
|
||||||
|
Executable
+155
@@ -0,0 +1,155 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Visualize resource usage timelines from benchmark data.
|
||||||
|
Generates SVG charts showing memory and CPU usage over time for each language.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg') # Non-interactive backend
|
||||||
|
from pathlib import Path
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def read_timeline_data(filepath):
|
||||||
|
"""Read timeline TSV file and return time, memory, cpu arrays."""
|
||||||
|
times = []
|
||||||
|
memories = []
|
||||||
|
cpus = []
|
||||||
|
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
parts = line.strip().split()
|
||||||
|
if len(parts) >= 3:
|
||||||
|
try:
|
||||||
|
times.append(int(parts[0]))
|
||||||
|
memories.append(int(parts[1]) / 1024) # Convert KB to MB
|
||||||
|
cpus.append(int(parts[2]))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return np.array(times), np.array(memories), np.array(cpus)
|
||||||
|
|
||||||
|
def create_timeline_chart(language, times, memories, cpus, output_dir):
|
||||||
|
"""Create a dual-axis chart showing memory and CPU over time."""
|
||||||
|
if len(times) == 0:
|
||||||
|
print(f"No data for {language}")
|
||||||
|
return
|
||||||
|
|
||||||
|
fig, ax1 = plt.subplots(figsize=(10, 6))
|
||||||
|
|
||||||
|
# Memory on left axis
|
||||||
|
color1 = '#2E86AB' # Blue
|
||||||
|
ax1.set_xlabel('Tid (ms)', fontsize=12)
|
||||||
|
ax1.set_ylabel('Minne (MB)', color=color1, fontsize=12)
|
||||||
|
ax1.plot(times, memories, color=color1, linewidth=2, label='Minne')
|
||||||
|
ax1.tick_params(axis='y', labelcolor=color1)
|
||||||
|
ax1.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
# CPU on right axis
|
||||||
|
ax2 = ax1.twinx()
|
||||||
|
color2 = '#E94F37' # Red
|
||||||
|
ax2.set_ylabel('CPU (%)', color=color2, fontsize=12)
|
||||||
|
ax2.plot(times, cpus, color=color2, linewidth=2, linestyle='--', label='CPU')
|
||||||
|
ax2.tick_params(axis='y', labelcolor=color2)
|
||||||
|
|
||||||
|
# Title and legend
|
||||||
|
plt.title(f'{language} - Resursanvändning över tid', fontsize=14, fontweight='bold')
|
||||||
|
|
||||||
|
# Combine legends
|
||||||
|
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||||
|
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||||
|
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper right')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
# Save
|
||||||
|
output_file = output_dir / f'{language}_timeline.svg'
|
||||||
|
plt.savefig(output_file, format='svg', dpi=300)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
print(f"Created {output_file}")
|
||||||
|
|
||||||
|
def create_comparison_chart(languages_data, output_dir, metric='memory'):
|
||||||
|
"""Create a comparison chart for multiple languages."""
|
||||||
|
fig, ax = plt.subplots(figsize=(14, 8))
|
||||||
|
|
||||||
|
colors = plt.cm.tab20(np.linspace(0, 1, len(languages_data)))
|
||||||
|
|
||||||
|
for idx, (language, times, values) in enumerate(languages_data):
|
||||||
|
if len(times) > 0:
|
||||||
|
ax.plot(times, values, label=language, linewidth=2, color=colors[idx])
|
||||||
|
|
||||||
|
if metric == 'memory':
|
||||||
|
ax.set_ylabel('Minne (MB)', fontsize=12)
|
||||||
|
ax.set_title('Minnesanvändning över tid - Jämförelse', fontsize=14, fontweight='bold')
|
||||||
|
else:
|
||||||
|
ax.set_ylabel('CPU (%)', fontsize=12)
|
||||||
|
ax.set_title('CPU-användning över tid - Jämförelse', fontsize=14, fontweight='bold')
|
||||||
|
|
||||||
|
ax.set_xlabel('Tid (ms)', fontsize=12)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
ax.legend(loc='upper right', fontsize=10)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
output_file = output_dir / f'comparison_{metric}.svg'
|
||||||
|
plt.savefig(output_file, format='svg', dpi=300)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
print(f"Created {output_file}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python visualize_timelines.py <timelines_dir> [output_dir]")
|
||||||
|
print("Example: python visualize_timelines.py timelines charts")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
timelines_dir = Path(sys.argv[1])
|
||||||
|
output_dir = Path(sys.argv[2]) if len(sys.argv) > 2 else Path('charts')
|
||||||
|
|
||||||
|
if not timelines_dir.exists():
|
||||||
|
print(f"Error: Directory {timelines_dir} does not exist")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Collect data for all languages
|
||||||
|
all_languages = []
|
||||||
|
|
||||||
|
# Process each language
|
||||||
|
for lang_dir in sorted(timelines_dir.iterdir()):
|
||||||
|
if lang_dir.is_dir():
|
||||||
|
language = lang_dir.name
|
||||||
|
|
||||||
|
# Find the best run (usually run_2.tsv)
|
||||||
|
timeline_file = lang_dir / 'run_2.tsv'
|
||||||
|
|
||||||
|
if timeline_file.exists():
|
||||||
|
times, memories, cpus = read_timeline_data(timeline_file)
|
||||||
|
|
||||||
|
if len(times) > 0:
|
||||||
|
# Create individual chart
|
||||||
|
create_timeline_chart(language, times, memories, cpus, output_dir)
|
||||||
|
|
||||||
|
# Store for comparison
|
||||||
|
all_languages.append((language, times, memories))
|
||||||
|
|
||||||
|
# Create comparison charts (top 10 fastest languages)
|
||||||
|
if len(all_languages) > 0:
|
||||||
|
# Sort by execution time and take top 10
|
||||||
|
all_languages.sort(key=lambda x: x[1][-1] if len(x[1]) > 0 else float('inf'))
|
||||||
|
top_10 = all_languages[:10]
|
||||||
|
|
||||||
|
create_comparison_chart(top_10, output_dir, 'memory')
|
||||||
|
|
||||||
|
# Create comparison for slowest languages
|
||||||
|
if len(all_languages) > 10:
|
||||||
|
slowest = all_languages[-5:]
|
||||||
|
create_comparison_chart(slowest, output_dir, 'memory')
|
||||||
|
|
||||||
|
print(f"\nAll charts saved to {output_dir}/")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user