package benchmark import ( "fmt" "io" "strings" "time" ) // Report fasst mehrere Messläufe zusammen. type Report struct { // Title benennt die Messreihe. Title string `json:"title"` // Machine ist die Maschine aller Läufe. Machine MachineProfile `json:"machine"` // Results sind die einzelnen Läufe. Results []*Result `json:"results"` // SkippedScenarios benennen nicht durchgeführte Szenarien samt Grund. // // Sie stehen gleichberechtigt neben den Ergebnissen: Ein Messbericht, der // verschweigt, was er nicht gemessen hat, liest sich vollständig und ist es // nicht. Dieselbe Regel wie bei den ungeprüften Bereichen im Security // Center (Phase 15). SkippedScenarios []SkippedScenario `json:"skipped_scenarios,omitempty"` // GeneratedAt ist der Zeitpunkt des Berichts in UTC. GeneratedAt time.Time `json:"generated_at"` } // SkippedScenario ist ein Szenario, das nicht gemessen werden konnte. type SkippedScenario struct { // Name benennt das Szenario. Name string `json:"name"` // Reason erklärt, warum es nicht gemessen wurde. Reason string `json:"reason"` } // AddSkipped vermerkt ein nicht gemessenes Szenario. func (report *Report) AddSkipped(scenarioName string, reason string) { report.SkippedScenarios = append(report.SkippedScenarios, SkippedScenario{Name: scenarioName, Reason: reason}) } // WriteText schreibt den Bericht in lesbarer Form. func (report *Report) WriteText(outputWriter io.Writer) { fmt.Fprintf(outputWriter, "%s\n%s\n\n", report.Title, strings.Repeat("=", len(report.Title))) fmt.Fprintf(outputWriter, "Maschine: %s/%s, %d Kerne (genutzt: %d), %s\n", report.Machine.OperatingSystem, report.Machine.Architecture, report.Machine.LogicalCPUs, report.Machine.GoMaxProcs, report.Machine.GoVersion) if report.Machine.MemoryLimitBytes > 0 { fmt.Fprintf(outputWriter, "Speichergrenze: %s\n", FormatBytes(float64(report.Machine.MemoryLimitBytes))) } fmt.Fprintf(outputWriter, "Gemessen: %s\n\n", report.GeneratedAt.Format(time.RFC3339)) for _, result := range report.Results { writeResultText(outputWriter, result) } if len(report.SkippedScenarios) > 0 { fmt.Fprintf(outputWriter, "\nNicht gemessen\n--------------\n") for _, skipped := range report.SkippedScenarios { fmt.Fprintf(outputWriter, " %s\n %s\n", skipped.Name, skipped.Reason) } } } // writeResultText schreibt einen einzelnen Lauf. func writeResultText(outputWriter io.Writer, result *Result) { fmt.Fprintf(outputWriter, "%s\n", result.ScenarioName) fmt.Fprintf(outputWriter, " %s\n", result.Description) fmt.Fprintf(outputWriter, " Daten: %s (%s, %d Dateien)\n", result.Data.Description, FormatBytes(float64(result.Data.TotalBytes)), result.Data.FileCount) fmt.Fprintf(outputWriter, " Dauer: %.2f s\n", result.Usage.WallClockSeconds) // Der Durchsatz erscheint nur bei inkompressiblen Daten. Bei // komprimierbaren misst er die Kompression — und wer die Zahl aus dem // Bericht zitiert, zitiert dann etwas anderes, als er meint. if !result.Data.Compressible { fmt.Fprintf(outputWriter, " Durchsatz: %s/s\n", FormatBytes(result.ThroughputBytesPerSecond())) } else { fmt.Fprintf(outputWriter, " Durchsatz: nicht ausgewiesen (komprimierbare Daten — "+ "die Zahl beschriebe die Kompression)\n") } if result.FilesProcessed > 0 { fmt.Fprintf(outputWriter, " Objekte: %d (%.0f je Sekunde)\n", result.FilesProcessed, result.FilesPerSecond()) } if result.BytesStored > 0 { fmt.Fprintf(outputWriter, " Abgelegt: %s\n", FormatBytes(float64(result.BytesStored))) } fmt.Fprintf(outputWriter, " CPU: %.2f s Programm + %.2f s System "+ "(%.1f Kerne ausgelastet)\n", result.Usage.UserCPUSeconds, result.Usage.SystemCPUSeconds, result.Usage.CPUEfficiency()) fmt.Fprintf(outputWriter, " Speicher: %s Höchststand, %s angefordert, %d Bereinigungen\n", FormatBytes(float64(result.Usage.PeakMemoryBytes)), FormatBytes(float64(result.Usage.AllocatedBytes)), result.Usage.GarbageCollections) for _, note := range result.Notes { fmt.Fprintf(outputWriter, " Hinweis: %s\n", note) } fmt.Fprintln(outputWriter) } // FormatBytes stellt eine Datenmenge lesbar dar. func FormatBytes(byteCount float64) string { const unitStep = 1024.0 if byteCount < unitStep { return fmt.Sprintf("%.0f B", byteCount) } unitNames := []string{"KiB", "MiB", "GiB", "TiB"} scaledValue := byteCount / unitStep for _, unitName := range unitNames { if scaledValue < unitStep { return fmt.Sprintf("%.1f %s", scaledValue, unitName) } scaledValue /= unitStep } return fmt.Sprintf("%.1f PiB", scaledValue) }