/
t3
/
cli
Обзор
Документация
Войти
/
t3
/
cli
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/controller/report.go
204 строки
6 KB
Ivan
fix report.json
09 июл 2026, 16:26
09 июл 2026, 16:26
65f0403
Код
Авторство
О чём код?
package controller import ( "encoding/json" "fmt" "log/slog" "os" "path/filepath" "time" "gitverse.ru/t3/cli/internal/config" pb "gitverse.ru/t3/cli/pkg/pb" ) // Report contains the full test execution report. type Report struct { GeneratedAt time.Time `json:"generated_at"` ConfigName string `json:"config_name"` RunID string `json:"run_id"` Status string `json:"status"` // "success" or "failed" Error string `json:"error,omitempty"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration string `json:"duration"` Steps []StepReport `json:"steps"` Agents []AgentReport `json:"agents"` } // StepReport contains information about a single step. type StepReport struct { Name string `json:"name"` Duration string `json:"duration"` Users int32 `json:"users"` Status string `json:"status"` // "active", "completed", "failed" } // AgentReport contains aggregated metrics for a single agent. type AgentReport struct { Address string `json:"address"` Hostname string `json:"hostname"` Weight int `json:"weight"` TotalIterations int64 `json:"total_iterations"` ErrorIterations int64 `json:"error_iterations"` ErrorRate float64 `json:"error_rate"` LogFile string `json:"log_file"` CustomMetrics map[string]any `json:"custom_metrics,omitempty"` } // generateReport creates a report file in the output directory. func generateReport(logger *slog.Logger, outputDir, runID string, cfg *config.Config, startTime time.Time, endTime time.Time, err error, agentMetrics []*agentMetrics) { report := Report{ GeneratedAt: time.Now(), ConfigName: cfg.Name, RunID: runID, StartTime: startTime, EndTime: endTime, Duration: endTime.Sub(startTime).Round(time.Second).String(), } if err != nil { report.Status = "failed" report.Error = err.Error() } else { report.Status = "success" } // Build steps summary report.Steps = make([]StepReport, len(cfg.Steps)) for i, step := range cfg.Steps { report.Steps[i] = StepReport{ Name: step.Stage, Duration: step.Duration.String(), Users: step.Users, Status: "completed", } } // Build agent summaries (basic — from config, enriched with log file paths and final metrics) report.Agents = make([]AgentReport, len(cfg.Agents)) for i, agent := range cfg.Agents { agentReport := AgentReport{ Address: agent.Address, Weight: agent.Weight, } // Build file paths relative to output dir safeAddr := sanitizeFilename(agent.Address) agentReport.LogFile = safeAddr + ".log" // Populate from final metrics snapshot if i < len(agentMetrics) && agentMetrics[i] != nil { m := agentMetrics[i] agentReport.Hostname = m.hostname agentReport.TotalIterations = m.totalIterations agentReport.ErrorIterations = m.errorIterations if m.totalIterations > 0 { agentReport.ErrorRate = float64(m.errorIterations) / float64(m.totalIterations) * 100.0 } if len(m.customMetrics) > 0 { customMap := make(map[string]any, len(m.customMetrics)) for _, cm := range m.customMetrics { key := cm.Name if len(cm.Tags) > 0 { key += "[" + tagMapString(cm.Tags) + "]" } customMap[key] = metricValueToSimple(cm) } agentReport.CustomMetrics = customMap } } report.Agents[i] = agentReport } // Write report.json reportPath := filepath.Join(outputDir, "report.json") data, err := json.MarshalIndent(report, "", " ") if err != nil { logger.Warn("failed to marshal report", "error", err) return } if err := os.WriteFile(reportPath, data, 0644); err != nil { logger.Warn("failed to write report", "path", reportPath, "error", err) return } logger.Info("report generated", "path", reportPath) } // metricValueToSimple converts a CustomMetric to a simple JSON-friendly value. func metricValueToSimple(cm *pb.CustomMetric) any { switch cm.Type { case "counter": return cm.Value.GetCounter() case "gauge": return cm.Value.GetGauge() case "histogram": h := cm.Value.GetHistogram() if h == nil { return map[string]any{"count": 0} } avg := 0.0 if h.Count > 0 { avg = h.Sum / float64(h.Count) } result := map[string]any{ "count": h.Count, "sum": h.Sum, "avg": avg, "min": h.Min, "max": h.Max, } if len(h.Quantiles) > 0 { quantiles := make(map[string]float64, len(h.Quantiles)) for pct, val := range h.Quantiles { quantiles[fmt.Sprintf("p%d", pct)] = val } result["quantiles"] = quantiles } return result default: return cm.Value.String() } } // sanitizeFilename replaces characters that are problematic in filenames. func sanitizeFilename(name string) string { result := make([]byte, 0, len(name)) for _, c := range []byte(name) { if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' { result = append(result, c) } else { result = append(result, '_') } } return string(result) } // ensureDir creates the output directory if it doesn't exist. func ensureDir(dir string) error { return os.MkdirAll(dir, 0755) } // controllerLogWriter returns a file writer for the controller log. func controllerLogWriter(outputDir string) (*os.File, error) { path := filepath.Join(outputDir, "controller.log") f, err := os.Create(path) if err != nil { return nil, fmt.Errorf("create controller log: %w", err) } return f, nil } // agentLogWriter returns a file writer for an agent log. func agentLogWriter(outputDir, agentAddr string) (*os.File, error) { safeAddr := sanitizeFilename(agentAddr) path := filepath.Join(outputDir, safeAddr+".log") f, err := os.Create(path) if err != nil { return nil, fmt.Errorf("create agent log: %w", err) } return f, nil }