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:
+14
-21
@@ -123,21 +123,22 @@ 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=$!
|
||||
|
||||
# Profile resources in background
|
||||
local resources=$(profile_resources "$pid" "$mem_profile_file" "$cpu_profile_file")
|
||||
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}')
|
||||
|
||||
@@ -145,11 +146,15 @@ run_program() {
|
||||
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 ))
|
||||
|
||||
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,24 +169,12 @@ 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
|
||||
fi
|
||||
done
|
||||
|
||||
# Calculate averages
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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