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:
Ein Anderssono
2026-04-23 01:00:42 +02:00
parent 83d3c1ba46
commit 8723db1033
40 changed files with 296 additions and 37 deletions
+27 -34
View File
@@ -123,33 +123,38 @@ run_program() {
local result
local peak_memory=0
local peak_cpu=0
local timeline_dir="timelines/$name"
# Create timeline directory
mkdir -p "$timeline_dir"
# Run 4 times, discard first run (warmup)
for i in 1 2 3 4; do
local mem_profile_file="/tmp/${name}_mem_$$_$i"
local cpu_profile_file="/tmp/${name}_cpu_$$_$i"
local timeline_file="$timeline_dir/run_$i.tsv"
local start=$(date +%s%N)
# Run program and capture PID for resource profiling
if [ "$i" -gt 1 ]; then
# For runs 2-4, profile memory and CPU
"$@" 2>/dev/null &
local pid=$!
"$@" 2>/dev/null &
local pid=$!
# Profile resources in background
local resources=$(profile_resources "$pid" "$mem_profile_file" "$cpu_profile_file")
local peak_mem=$(echo "$resources" | awk '{print $1}')
local peak_cpu_val=$(echo "$resources" | awk '{print $2}')
# 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=$?
# 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 ))
local end=$(date +%s%N)
local elapsed=$(( (end - start) / 1000000 ))
if [ $exit_code -eq 0 ]; then
# Get result for verification
result=$("$@" 2>/dev/null)
if [ "$i" -gt 1 ]; then
# Only count runs 2-4 for averages
total_time=$((total_time + elapsed))
total_memory=$((total_memory + peak_mem))
total_cpu=$((total_cpu + peak_cpu_val))
@@ -164,23 +169,11 @@ run_program() {
if verify "$result" "$DECIMALS"; then
((success_count++))
fi
else
echo -e "${RED}ERROR${NC}"
results+=("999999 $name ERROR")
rm -f "$mem_profile_file" "$cpu_profile_file"
return
fi
rm -f "$mem_profile_file" "$cpu_profile_file"
else
# First run (warmup) - just execute without profiling
if result=$("$@" 2>/dev/null); then
:
else
echo -e "${RED}ERROR${NC}"
results+=("999999 $name ERROR")
return
fi
echo -e "${RED}ERROR${NC}"
results+=("999999 $name ERROR")
return
fi
done
@@ -194,10 +187,10 @@ run_program() {
local peak_memory_mb=$((peak_memory / 1024))
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")
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")
fi
}
+1
View File
@@ -0,0 +1 @@
13 2000 0
1 13 2000 0
+1
View File
@@ -0,0 +1 @@
11 2000 0
1 11 2000 0
+1
View File
@@ -0,0 +1 @@
16 2000 0
1 16 2000 0
+1
View File
@@ -0,0 +1 @@
11 2000 0
1 11 2000 0
+3
View File
@@ -0,0 +1,3 @@
11 2000 0
35 2016 0
59 2016 0
1 11 2000 0
2 35 2016 0
3 59 2016 0
+2
View File
@@ -0,0 +1,2 @@
8 2000 0
29 0 0
1 8 2000 0
2 29 0 0
+2
View File
@@ -0,0 +1,2 @@
9 2000 0
34 2000
1 9 2000 0
2 34 2000
+2
View File
@@ -0,0 +1,2 @@
8 2000 0
30 2000 0
1 8 2000 0
2 30 2000 0
+1
View File
@@ -0,0 +1 @@
11 240
1 11 240
View File
View File
View File
+1
View File
@@ -0,0 +1 @@
7 0 0
1 7 0 0
View File
View File
View File
+5
View File
@@ -0,0 +1,5 @@
13 2032 0
40 2032 0
64 2032 0
90 2032 0
116 2032 0
1 13 2032 0
2 40 2032 0
3 64 2032 0
4 90 2032 0
5 116 2032 0
+2
View File
@@ -0,0 +1,2 @@
9 2016 0
33 2016 0
1 9 2016 0
2 33 2016 0
+2
View File
@@ -0,0 +1,2 @@
8 2032 0
33 2032 0
1 8 2032 0
2 33 2032 0
+2
View File
@@ -0,0 +1,2 @@
8 2000 0
33 2016 0
1 8 2000 0
2 33 2016 0
+1
View File
@@ -0,0 +1 @@
12 2288
1 12 2288
+1
View File
@@ -0,0 +1 @@
6 0 0
1 6 0 0
+1
View File
@@ -0,0 +1 @@
6 0 0
1 6 0 0
+1
View File
@@ -0,0 +1 @@
5 0 0
1 5 0 0
+1
View File
@@ -0,0 +1 @@
10 464 0
1 10 464 0
+1
View File
@@ -0,0 +1 @@
8 496
1 8 496
+1
View File
@@ -0,0 +1 @@
8 496
1 8 496
+1
View File
@@ -0,0 +1 @@
8 496
1 8 496
+2
View File
@@ -0,0 +1,2 @@
12 5344 0
50 0 0
1 12 5344 0
2 50 0 0
+1
View File
@@ -0,0 +1 @@
12 9088 0
1 12 9088 0
+1
View File
@@ -0,0 +1 @@
13 10624 0
1 13 10624 0
+1
View File
@@ -0,0 +1 @@
12 10016 0
1 12 10016 0
+16
View File
@@ -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
1 15 2032 0
2 42 2032 0
3 65 2032 0
4 94 2032 0
5 119 2032 0
6 146 2032 0
7 169 2032 0
8 193 2032 0
9 221 2032 0
10 246 2032 0
11 270 2032 0
12 296 2032 0
13 319 2032 0
14 343 2032 0
15 367 2032 0
16 393 2032 0
+15
View File
@@ -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
1 9 2032 0
2 34 2032 0
3 62 2032 0
4 85 2032 0
5 109 2032 0
6 135 2032 0
7 158 2032 0
8 181 2032 0
9 213 2032 0
10 238 2032 0
11 262 2032 0
12 288 2032 0
13 311 2032 0
14 336 2032 0
15 359 2032 0
+13
View File
@@ -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
1 9 2032 0
2 31 2032 0
3 57 2032 0
4 79 2032 0
5 102 2032 0
6 126 2032 0
7 155 2032 0
8 179 2032 0
9 202 2032 0
10 227 2032 0
11 249 2032 0
12 272 2032 0
13 296 2032 0
+12
View File
@@ -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
1 8 2048 0
2 31 2048 0
3 57 2048 0
4 82 2048 0
5 106 2048 0
6 130 2048 0
7 155 2048 0
8 179 2048 0
9 206 2048 0
10 230 2048 0
11 256 2048 0
12 279 2048 0
+6
View File
@@ -0,0 +1,6 @@
12 2048 0
36 2048 0
60 2048 0
84 2048 0
108 2048 0
132 0 0
1 12 2048 0
2 36 2048 0
3 60 2048 0
4 84 2048 0
5 108 2048 0
6 132 0 0
+10
View File
@@ -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
1 8 2032 0
2 33 2032 0
3 56 2032 0
4 79 2032 0
5 102 2032 0
6 128 2032 0
7 151 2032 0
8 177 2032 0
9 206 2032 0
10 227 2032 0
+155
View File
@@ -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()