straceanalyse.py aktualisiert

This commit is contained in:
2025-10-27 16:28:49 +01:00
parent 6d6c9a4251
commit 341bab0345

View File

@@ -22,22 +22,22 @@ class ShopPerformanceAnalyzer:
'file_paths': Counter(),
'errors': Counter()
}
self.debug = False # Weniger Output bei vielen Prozessen
self.debug = False
self.output_dir = f"/root/shop_analysis_{domain}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
def get_php_fpm_pids(self):
"""Finde alle PHP-FPM PIDs für den Shop"""
"""Finde alle PHP-FPM PIDs fuer den Shop"""
try:
cmd = f"ps aux | grep 'php-fpm: pool {self.domain}' | grep -v grep | awk '{{print $2}}'"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
pids = [int(pid) for pid in result.stdout.strip().split('\n') if pid]
return pids
except Exception as e:
print(f"Fehler beim Finden der PIDs: {e}")
print(f"Fehler beim Finden der PIDs: {e}")
return []
def run_strace(self, pid, duration=5):
"""Führe strace auf einem Prozess aus"""
"""Fuehre strace auf einem Prozess aus"""
try:
cmd = [
'strace',
@@ -137,10 +137,10 @@ class ShopPerformanceAnalyzer:
os.makedirs(self.output_dir, exist_ok=True)
# 1. Komplette Liste (sortiert nach Häufigkeit)
# 1. Komplette Liste (sortiert nach Haeufigkeit)
list_file = os.path.join(self.output_dir, 'missing_files_all.txt')
with open(list_file, 'w') as f:
f.write(f"# Fehlende Dateien für {self.domain}\n")
f.write(f"# Fehlende Dateien fuer {self.domain}\n")
f.write(f"# Erstellt: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"# Total: {len(self.results['missing_files'])} Dateien\n")
f.write(f"# Zugriffe: {sum(self.results['missing_files'].values())}\n")
@@ -192,7 +192,7 @@ class ShopPerformanceAnalyzer:
f.write(f'SHOP_ROOT="/var/www/vhosts/{self.domain}/httpdocs"\n')
f.write('PLACEHOLDER="$SHOP_ROOT/gfx/keinBild.gif"\n\n')
f.write('if [ ! -f "$PLACEHOLDER" ]; then\n')
f.write(' echo "Fehler: Placeholder nicht gefunden: $PLACEHOLDER"\n')
f.write(' echo "Fehler: Placeholder nicht gefunden: $PLACEHOLDER"\n')
f.write(' exit 1\n')
f.write('fi\n\n')
f.write('echo "Erstelle fehlende Dateien..."\n')
@@ -217,9 +217,9 @@ class ShopPerformanceAnalyzer:
f.write(f'fi\n')
f.write('\necho ""\n')
f.write('echo "Fertig!"\n')
f.write('echo "Fertig!"\n')
f.write('echo " Erstellt: $CREATED Platzhalter"\n')
f.write('echo " Übersprungen: $SKIPPED (existieren bereits)"\n')
f.write('echo " Uebersprungen: $SKIPPED (existieren bereits)"\n')
f.write(f'echo " Total Dateien: {len(image_files)}"\n')
os.chmod(script_file, 0o755)
@@ -257,11 +257,11 @@ class ShopPerformanceAnalyzer:
with open(manufacturer_file, 'w') as f:
f.write(f"# Hersteller IDs mit fehlenden Bildern\n")
f.write(f"# Total: {len(manufacturer_ids)} Hersteller\n")
f.write(f"# Verwendung: Im JTL-Shop Admin diese Hersteller prüfen\n\n")
f.write(f"# Verwendung: Im JTL-Shop Admin diese Hersteller pruefen\n\n")
for mid in sorted(manufacturer_ids, key=int):
f.write(f"{mid}\n")
# 6. Nur Dateipfade (für weitere Verarbeitung)
# 6. Nur Dateipfade (fuer weitere Verarbeitung)
paths_only_file = os.path.join(self.output_dir, 'missing_files_paths_only.txt')
with open(paths_only_file, 'w') as f:
for filepath in self.results['missing_files'].keys():
@@ -272,36 +272,36 @@ class ShopPerformanceAnalyzer:
def generate_report(self):
"""Generiere Analyse-Report"""
print("\n" + "="*80)
print(f"🔍 PERFORMANCE ANALYSE: {self.domain}")
print(f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"PERFORMANCE ANALYSE: {self.domain}")
print(f"Datum: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*80 + "\n")
total_syscalls = sum(self.results['syscalls'].values())
if total_syscalls == 0:
print("⚠️ WARNUNG: Keine Syscalls aufgezeichnet!")
print(" Mögliche Gründe:")
print("WARNUNG: Keine Syscalls aufgezeichnet!")
print(" Moegliche Gruende:")
print(" - Prozesse sind gerade idle (wenig Traffic)")
print(" - Strace hat keine Berechtigung")
print(" - Prozesse wurden zwischen Analyse beendet\n")
print(" Versuche: python3 script.py spiel-und-modellbau.com 10\n")
return
print("📊 SYSCALL STATISTIK")
print("SYSCALL STATISTIK")
print("-" * 80)
for syscall, count in self.results['syscalls'].most_common(15):
percentage = (count / total_syscalls * 100) if total_syscalls > 0 else 0
bar = '' * int(percentage / 2)
bar = '#' * int(percentage / 2)
print(f" {syscall:20s}: {count:6d} ({percentage:5.1f}%) {bar}")
print()
# Fehlende Dateien
missing_count = len(self.results['missing_files'])
if missing_count > 0:
print("FEHLENDE DATEIEN (ENOENT)")
print("FEHLENDE DATEIEN (ENOENT)")
print("-" * 80)
print(f" ⚠️ {missing_count} verschiedene Dateien nicht gefunden!")
print(f" ⚠️ {sum(self.results['missing_files'].values())} Zugriffe auf nicht-existierende Dateien!\n")
print(f" WARNUNG: {missing_count} verschiedene Dateien nicht gefunden!")
print(f" WARNUNG: {sum(self.results['missing_files'].values())} Zugriffe auf nicht-existierende Dateien!\n")
# Kategorien
categories = defaultdict(int)
@@ -319,7 +319,7 @@ class ShopPerformanceAnalyzer:
print(" Kategorien:")
for category, count in sorted(categories.items(), key=lambda x: x[1], reverse=True):
print(f" {category:25s}: {count:4d} Dateien")
print(f" * {category:25s}: {count:4d} Dateien")
print()
print(" Top 15 fehlende Dateien:")
@@ -328,13 +328,13 @@ class ShopPerformanceAnalyzer:
print(f" [{count:3d}x] {short_path}")
if len(self.results['missing_files']) > 15:
print(f"\n ... und {len(self.results['missing_files'])-15} weitere")
print(f" Vollständige Liste siehe Export-Dateien!")
print(f"\n INFO: ... und {len(self.results['missing_files'])-15} weitere")
print(f" INFO: Vollstaendige Liste siehe Export-Dateien!")
print()
# Errors
if self.results['errors']:
print("⚠️ FEHLER")
print("FEHLER")
print("-" * 80)
for error, count in self.results['errors'].items():
print(f" {error:20s}: {count:6d}x")
@@ -342,19 +342,19 @@ class ShopPerformanceAnalyzer:
# MySQL Queries
if self.results['mysql_queries']:
print("🗄️ MYSQL QUERIES")
print("MYSQL QUERIES")
print("-" * 80)
query_counter = Counter(self.results['mysql_queries'])
print(f" Total Queries: {len(self.results['mysql_queries'])}")
print(f" Unique Queries: {len(query_counter)}")
print("\n Häufigste Queries:")
print("\n Haeufigste Queries:")
for query, count in query_counter.most_common(10):
print(f" [{count:3d}x] {query[:70]}...")
print()
# File Paths
if self.results['file_paths']:
print("📁 HÄUFIGSTE DATEIZUGRIFFE")
print("HAEUFIGSTE DATEIZUGRIFFE")
print("-" * 80)
for path, count in self.results['file_paths'].most_common(15):
print(f" [{count:3d}x] {path}")
@@ -362,7 +362,7 @@ class ShopPerformanceAnalyzer:
# Redis
if self.results['redis_operations']:
print("🔴 REDIS OPERATIONEN")
print("REDIS OPERATIONEN")
print("-" * 80)
total_redis = sum(self.results['redis_operations'].values())
for op, count in self.results['redis_operations'].most_common():
@@ -372,29 +372,29 @@ class ShopPerformanceAnalyzer:
# Slow Operations
if self.results['slow_operations']:
print("🐌 LANGSAME OPERATIONEN")
print("LANGSAME OPERATIONEN")
print("-" * 80)
slow_counter = Counter(self.results['slow_operations'])
for op, count in slow_counter.items():
print(f" ⚠️ {op}: {count}x")
print(f" WARNUNG: {op}: {count}x")
print()
# Export fehlende Dateien
if missing_count > 0:
print("="*80)
print("💾 EXPORTIERE FEHLENDE DATEIEN")
print("EXPORTIERE FEHLENDE DATEIEN")
print("="*80 + "\n")
export_dir = self.export_missing_files()
if export_dir:
print(f"Dateien exportiert nach: {export_dir}\n")
print(f"Dateien exportiert nach: {export_dir}\n")
print(" Erstellt:")
print(f" 📄 missing_files_all.txt - Komplette Liste (sortiert nach Häufigkeit)")
print(f" 📁 missing_files_by_category.txt - Nach Kategorie gruppiert")
print(f" 📊 missing_files.csv - CSV für Excel")
print(f" 🔧 create_placeholders.sh - Bash Script (ausführbar)")
print(f" 🏷️ missing_manufacturer_ids.txt - Hersteller IDs")
print(f" 📝 missing_files_paths_only.txt - Nur Pfade (für Scripts)\n")
print(f" missing_files_all.txt - Komplette Liste (sortiert nach Haeufigkeit)")
print(f" missing_files_by_category.txt - Nach Kategorie gruppiert")
print(f" missing_files.csv - CSV fuer Excel")
print(f" create_placeholders.sh - Bash Script (ausfuehrbar)")
print(f" missing_manufacturer_ids.txt - Hersteller IDs")
print(f" missing_files_paths_only.txt - Nur Pfade (fuer Scripts)\n")
print(" Quick-Fix:")
print(f" bash {export_dir}/create_placeholders.sh\n")
@@ -405,7 +405,7 @@ class ShopPerformanceAnalyzer:
def generate_recommendations(self, total_syscalls, missing_count):
"""Generiere Handlungsempfehlungen"""
print("="*80)
print("💡 HANDLUNGSEMPFEHLUNGEN FÜR DEN KUNDEN")
print("HANDLUNGSEMPFEHLUNGEN FUER DEN KUNDEN")
print("="*80 + "\n")
recommendations = []
@@ -418,10 +418,10 @@ class ShopPerformanceAnalyzer:
if manufacturer_missing > 0:
recommendations.append({
'priority': priority,
'severity': '🔥 KRITISCH',
'severity': 'KRITISCH',
'problem': f'{manufacturer_missing} Hersteller-Bilder fehlen',
'impact': f'Jedes fehlende Bild = 6-8 stat() Calls. Bei {sum(v for k,v in self.results["missing_files"].items() if "manufacturer" in k)} Zugriffen!',
'solution': '1. JTL-Shop Admin einloggen\n2. Bilder Hersteller-Bilder "Fehlende generieren"\n3. ODER: Bash Script ausführen (siehe Export)',
'solution': '1. JTL-Shop Admin einloggen\n2. Bilder -> Hersteller-Bilder -> "Fehlende generieren"\n3. ODER: Bash Script ausfuehren (siehe Export)',
'files': [f"{self.output_dir}/create_placeholders.sh",
f"{self.output_dir}/missing_manufacturer_ids.txt"]
})
@@ -430,10 +430,10 @@ class ShopPerformanceAnalyzer:
if product_missing > 0:
recommendations.append({
'priority': priority,
'severity': '⚠️ WICHTIG',
'severity': 'WICHTIG',
'problem': f'{product_missing} Produkt-Bilder fehlen',
'impact': 'Erhöhte I/O Last',
'solution': 'JTL-Shop Admin Bilder "Bildcache regenerieren"'
'impact': 'Erhoehte I/O Last',
'solution': 'JTL-Shop Admin -> Bilder -> "Bildcache regenerieren"'
})
priority += 1
@@ -442,11 +442,11 @@ class ShopPerformanceAnalyzer:
if stat_calls > 500:
recommendations.append({
'priority': priority,
'severity': '⚠️ WICHTIG',
'severity': 'WICHTIG',
'problem': f'{stat_calls} Filesystem stat() Calls',
'impact': 'Filesystem-Thrashing, langsame Response-Times',
'solution': 'PHP Realpath Cache erhöhen in PHP-Einstellungen',
'technical': 'Plesk Domain PHP Settings:\nrealpath_cache_size = 4096K\nrealpath_cache_ttl = 600'
'solution': 'PHP Realpath Cache erhoehen in PHP-Einstellungen',
'technical': 'Plesk -> Domain -> PHP Settings:\nrealpath_cache_size = 4096K\nrealpath_cache_ttl = 600'
})
priority += 1
@@ -454,20 +454,20 @@ class ShopPerformanceAnalyzer:
if imagemagick_count > 3:
recommendations.append({
'priority': priority,
'severity': '🔥 KRITISCH',
'severity': 'KRITISCH',
'problem': f'ImageMagick wird {imagemagick_count}x aufgerufen',
'impact': 'CPU-intensive Bildverarbeitung bei jedem Request!',
'solution': '1. Bild-Cache in JTL-Shop aktivieren\n2. Alle Bildgrößen vorher generieren\n3. Prüfen ob Bilder wirklich vorhanden sind'
'solution': '1. Bild-Cache in JTL-Shop aktivieren\n2. Alle Bildgroessen vorher generieren\n3. Pruefen ob Bilder wirklich vorhanden sind'
})
priority += 1
if len(self.results['mysql_queries']) > 50:
recommendations.append({
'priority': priority,
'severity': '⚠️ WICHTIG',
'severity': 'WICHTIG',
'problem': f'{len(self.results["mysql_queries"])} MySQL Queries',
'impact': 'N+1 Query Problem, Database Overhead',
'solution': 'JTL-Shop: System Cache Object Cache aktivieren (Redis)'
'solution': 'JTL-Shop: System -> Cache -> Object Cache aktivieren (Redis)'
})
priority += 1
@@ -475,41 +475,41 @@ class ShopPerformanceAnalyzer:
if eagain_count > 100:
recommendations.append({
'priority': priority,
'severity': '⚠️ WICHTIG',
'severity': 'WICHTIG',
'problem': f'{eagain_count}x EAGAIN',
'impact': 'Redis/MySQL Verbindungen überlastet',
'solution': 'Redis Connection Pool erhöhen oder PHP-FPM Worker erhöhen'
'impact': 'Redis/MySQL Verbindungen ueberlastet',
'solution': 'Redis Connection Pool erhoehen oder PHP-FPM Worker erhoehen'
})
priority += 1
if recommendations:
for rec in recommendations:
print(f"{rec['severity']} PRIORITÄT {rec['priority']}: {rec['problem']}")
print(f" 📊 Impact: {rec['impact']}")
print(f" ✅ Lösung: {rec['solution']}")
print(f"[{rec['severity']}] PRIORITAET {rec['priority']}: {rec['problem']}")
print(f" Impact: {rec['impact']}")
print(f" Loesung: {rec['solution']}")
if 'files' in rec:
print(f" 📁 Dateien:")
print(f" Dateien:")
for file in rec['files']:
print(f" {file}")
print(f" * {file}")
if 'technical' in rec:
lines = rec['technical'].split('\n')
print(f" 🔧 Technisch:")
print(f" Technisch:")
for line in lines:
print(f" {line}")
print()
else:
print("Keine kritischen Probleme gefunden!\n")
print("Keine kritischen Probleme gefunden!\n")
print("="*80)
print("📋 ZUSAMMENFASSUNG")
print("ZUSAMMENFASSUNG")
print("="*80)
print(f" Total Syscalls: {total_syscalls}")
print(f" Fehlende Dateien: {missing_count}")
print(f" MySQL Queries: {len(self.results['mysql_queries'])}")
print(f" Redis Operations: {sum(self.results['redis_operations'].values())}")
print(f" Handlungsempfehlungen: {len(recommendations)}")
print(f" * Total Syscalls: {total_syscalls}")
print(f" * Fehlende Dateien: {missing_count}")
print(f" * MySQL Queries: {len(self.results['mysql_queries'])}")
print(f" * Redis Operations: {sum(self.results['redis_operations'].values())}")
print(f" * Handlungsempfehlungen: {len(recommendations)}")
if missing_count > 0:
print(f"\n 📁 Export-Verzeichnis: {self.output_dir}")
print(f"\n Export-Verzeichnis: {self.output_dir}")
print()
def main():
@@ -533,27 +533,27 @@ def main():
duration = int(sys.argv[2]) if len(sys.argv) > 2 else 5
max_processes = int(sys.argv[3]) if len(sys.argv) > 3 else None
print(f"\n🚀 Starte Performance-Analyse für: {domain}")
print(f"⏱️ Analyse-Dauer: {duration} Sekunden pro Prozess")
print(f"\nStarte Performance-Analyse fuer: {domain}")
print(f"Analyse-Dauer: {duration} Sekunden pro Prozess")
if max_processes:
print(f"🔢 Max Prozesse: {max_processes}")
print(f"Max Prozesse: {max_processes}")
else:
print(f"🔢 Prozesse: ALLE gefundenen PHP-FPM Worker")
print(f"Prozesse: ALLE gefundenen PHP-FPM Worker")
print()
analyzer = ShopPerformanceAnalyzer(domain)
pids = analyzer.get_php_fpm_pids()
if not pids:
print("Keine PHP-FPM Prozesse gefunden!")
print("Keine PHP-FPM Prozesse gefunden!")
sys.exit(1)
# Limit anwenden falls gesetzt
if max_processes and len(pids) > max_processes:
print(f"{len(pids)} PHP-FPM Prozesse gefunden (analysiere {max_processes})")
print(f"{len(pids)} PHP-FPM Prozesse gefunden (analysiere {max_processes})")
pids = pids[:max_processes]
else:
print(f"{len(pids)} PHP-FPM Prozesse gefunden (analysiere alle)")
print(f"{len(pids)} PHP-FPM Prozesse gefunden (analysiere alle)")
print(f" PIDs: {pids}\n")
@@ -562,7 +562,7 @@ def main():
analyzed = 0
failed = 0
print("🔄 Analyse läuft...")
print("Analyse laeuft...")
print("-" * 80)
for i, pid in enumerate(pids, 1):
@@ -570,7 +570,7 @@ def main():
percent = int((i / total) * 100)
bar_length = 40
filled = int((percent / 100) * bar_length)
bar = '' * filled + '' * (bar_length - filled)
bar = '#' * filled + '-' * (bar_length - filled)
print(f"\r[{bar}] {percent:3d}% | PID {pid:6d} ({i}/{total})", end='', flush=True)
@@ -581,17 +581,17 @@ def main():
else:
failed += 1
print(f"\r[{'' * bar_length}] 100% | Fertig!{' ' * 30}")
print(f"\r[{'#' * bar_length}] 100% | Fertig!{' ' * 30}")
print("-" * 80)
print(f"\nAnalyse abgeschlossen!")
print(f" Erfolgreich analysiert: {analyzed}/{total}")
print(f"\nAnalyse abgeschlossen!")
print(f" * Erfolgreich analysiert: {analyzed}/{total}")
if failed > 0:
print(f" Idle/Keine Daten: {failed}")
print(f" * Idle/Keine Daten: {failed}")
print()
if analyzed == 0:
print("⚠️ Konnte kesine Daten sammeln!")
print(" Shop hat gerade wenig Traffic. Versuche später nochmal oder erhöhe duration.\n")
print("Konnte keine Daten sammeln!")
print(" Shop hat gerade wenig Traffic. Versuche spaeter nochmal oder erhoehe duration.\n")
sys.exit(1)
analyzer.generate_report()