diff options
Diffstat (limited to '')
| -rw-r--r-- | cmd/git-leaderboard/main.go | 128 | ||||
| -rw-r--r-- | cmd/goget/main.go | 165 | ||||
| -rw-r--r-- | cmd/pviz/main.go | 266 | ||||
| -rw-r--r-- | cmd/seq-stat/main.go | 115 |
4 files changed, 674 insertions, 0 deletions
diff --git a/cmd/git-leaderboard/main.go b/cmd/git-leaderboard/main.go new file mode 100644 index 0000000..7c846a5 --- /dev/null +++ b/cmd/git-leaderboard/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "sort" + "strings" +) + +type Author struct { + Name string + Count int +} + +func main() { + lineFlag := flag.Bool("line", false, "Count lines changed instead of commits") + flag.Parse() + + if !isGitRepository() { + fmt.Println("Error: Not in a git repository") + os.Exit(1) + } + + authors, err := getAuthors(*lineFlag) + if err != nil { + fmt.Printf("Error getting authors: %v\n", err) + os.Exit(1) + } + + if len(authors) == 0 { + fmt.Println("No authors found. The repository might be empty or have no commits.") + os.Exit(0) + } + + sort.Slice(authors, func(i, j int) bool { + return authors[i].Count > authors[j].Count + }) + + maxCount := authors[0].Count + + fmt.Printf("%-30s %-10s %s\n", "Author", "Count", "Contribution") + fmt.Println(strings.Repeat("-", 80)) + + for _, author := range authors { + bar := generateBar(author.Count, maxCount) + fmt.Printf("%-30s %-10d %s\n", author.Name, author.Count, bar) + } +} + +func isGitRepository() bool { + cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree") + err := cmd.Run() + return err == nil +} + +func getAuthors(countLines bool) ([]Author, error) { + var cmd *exec.Cmd + + if countLines { + cmd = exec.Command("git", "log", "--pretty=format:%aN", "--numstat") + } else { + cmd = exec.Command("git", "shortlog", "-sn", "--no-merges", "HEAD") + } + + output, err := cmd.Output() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + return nil, fmt.Errorf("git command failed: %s. Error output: %s", err, exitErr.Stderr) + } + return nil, fmt.Errorf("error executing git command: %v", err) + } + + lines := strings.Split(string(output), "\n") + authors := make(map[string]int) + + if countLines { + currentAuthor := "" + for _, line := range lines { + if line == "" { + currentAuthor = "" + continue + } + if currentAuthor == "" { + currentAuthor = line + continue + } else { + fields := strings.Fields(line) + if len(fields) >= 2 { + added, _ := parseInt(fields[0]) + deleted, _ := parseInt(fields[1]) + authors[currentAuthor] += added + deleted + } + } + } + } else { + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 2 { + count, _ := parseInt(fields[0]) + name := strings.Join(fields[1:], " ") + authors[name] = count + } + } + } + + var result []Author + for name, count := range authors { + result = append(result, Author{Name: name, Count: count}) + } + return result, nil +} + +func parseInt(s string) (int, error) { + if s == "-" { + return 0, nil + } + var result int + _, err := fmt.Sscanf(s, "%d", &result) + return result, err +} + +func generateBar(count, maxCount int) string { + maxWidth := 38 + width := int(float64(count) / float64(maxCount) * float64(maxWidth)) + return strings.Repeat("▆", width) +} diff --git a/cmd/goget/main.go b/cmd/goget/main.go new file mode 100644 index 0000000..3f17448 --- /dev/null +++ b/cmd/goget/main.go @@ -0,0 +1,165 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "net/url" + "os" + "os/signal" + "slices" + "strings" + "syscall" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" + "golang.org/x/mod/semver" +) + +const ( + forgeDomain = "code.fcuny.net" + forgeUser = "fcuny" + goPkgDomain = "go.fcuny.net" +) + +var ( + goGetReqs = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "goget_requests_total", + Help: "go get requests processed, by repository name.", + }, []string{"name"}) + + modules = []string{"x"} +) + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + metricsMux := http.NewServeMux() + metricsMux.Handle("/metrics", promhttp.Handler()) + metricsServer := &http.Server{ + Addr: ":9091", Handler: metricsMux, + ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, + } + + go func() { + log.Println("Starting metrics server on :9091") + if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Metrics server error: %v", err) + } + }() + + s := &http.Server{ + Addr: ":8070", + Handler: handler(), + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 1 * time.Minute, + } + + go func() { + log.Println("Starting main server on :8080") + if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Main server error: %v", err) + } + }() + + <-ctx.Done() + log.Println("Shutdown signal received, starting graceful shutdown...") + + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer shutdownCancel() + + var shutdownErr error + + log.Println("Shutting down main server...") + if err := s.Shutdown(shutdownCtx); err != nil { + log.Printf("Main server shutdown error: %v", err) + shutdownErr = err + } + + log.Println("Shutting down metrics server...") + if err := metricsServer.Shutdown(shutdownCtx); err != nil { + log.Printf("Metrics server shutdown error: %v", err) + if shutdownErr == nil { + shutdownErr = err + } + } + + if shutdownErr != nil { + log.Printf("Shutdown completed with errors") + os.Exit(1) + } + + log.Println("Shutdown completed successfully") +} + +func handler() http.Handler { + mux := http.NewServeMux() + + mux.Handle(goPkgDomain+"/{name}", siteHandler(modules)) + mux.Handle(goPkgDomain+"/{name}/", siteHandler(modules)) + + goGetMux := http.NewServeMux() + for _, name := range modules { + module := goPkgDomain + "/" + name + goGetMux.Handle( + module+"/", + goImportHandler(module, "https://"+forgeDomain+"/"+forgeUser+"/"+name), + ) + } + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + status := http.StatusMethodNotAllowed + http.Error(rw, http.StatusText(status), status) + return + } + + if r.URL.Query().Get("go-get") == "1" { + goGetMux.ServeHTTP(rw, r) + return + } + mux.ServeHTTP(rw, r) + }) +} + +func goImportHandler(module, repo string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + goGetReqs.WithLabelValues(module).Inc() + w.Header().Set("Content-Type", "text/html; charset=UTF-8") + if _, err := fmt.Fprintf(w, `<head><meta name="go-import" content="%s git %s">`, module, repo); err != nil { + log.Printf("Error writing response: %v", err) + } + }) +} + +func siteHandler(names []string) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + name := r.PathValue("name") + name, version, hasV := strings.Cut(name, "@") + if !hasV { + name, _, _ = strings.Cut(name, ".") + } + if hasV && !semver.IsValid(version) || !slices.Contains(names, name) { + http.NotFound(rw, r) + return + } + u := &url.URL{ + Scheme: "https", + Host: forgeDomain, + Path: forgeUser + r.URL.Path, + } + if !hasV { + path, symbol, hasSymbol := strings.Cut(r.URL.Path, ".") + if hasSymbol { + u.Path = forgeUser + "/" + path + u.Fragment = symbol + } + } + http.Redirect(rw, r, u.String(), http.StatusFound) + }) +} diff --git a/cmd/pviz/main.go b/cmd/pviz/main.go new file mode 100644 index 0000000..89bd78d --- /dev/null +++ b/cmd/pviz/main.go @@ -0,0 +1,266 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "text/tabwriter" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +type Period string + +const ( + PeriodDay Period = "day" + PeriodWeek Period = "week" + PeriodMonth Period = "month" + PeriodQuarter Period = "quarter" + PeriodYear Period = "year" +) + +var periodMinutes = map[Period]float64{ + PeriodDay: 24 * 60, + PeriodWeek: 7 * 24 * 60, + PeriodMonth: 30 * 24 * 60, + PeriodQuarter: 90 * 24 * 60, + PeriodYear: 365 * 24 * 60, +} + +var periodOrder = []Period{PeriodDay, PeriodWeek, PeriodMonth, PeriodQuarter, PeriodYear} + +type TimeUnit struct { + Minutes float64 + Formatted string +} + +type AvailabilityCalculator struct { + availability float64 +} + +func NewAvailabilityCalculator(availabilityPercentage float64) (*AvailabilityCalculator, error) { + if availabilityPercentage < 0 || availabilityPercentage > 100 { + return nil, fmt.Errorf("availability must be between 0 and 100") + } + return &AvailabilityCalculator{ + availability: availabilityPercentage / 100.0, + }, nil +} + +func (ac *AvailabilityCalculator) CalculateDowntime(period Period) TimeUnit { + totalMinutes := periodMinutes[period] + downtimeMinutes := totalMinutes * (1 - ac.availability) + return TimeUnit{ + Minutes: downtimeMinutes, + Formatted: formatDuration(downtimeMinutes), + } +} + +func (ac *AvailabilityCalculator) CalculateAllPeriods() map[Period]TimeUnit { + result := make(map[Period]TimeUnit) + for _, period := range periodOrder { + result[period] = ac.CalculateDowntime(period) + } + return result +} + +func formatDuration(minutes float64) string { + if minutes < 1 { + return fmt.Sprintf("%.1f seconds", minutes*60) + } + + hours := int(minutes / 60) + remainingMinutes := minutes - float64(hours*60) + + if hours == 0 { + return fmt.Sprintf("%.1f minutes", remainingMinutes) + } else if remainingMinutes == 0 { + return fmt.Sprintf("%d hours", hours) + } else { + return fmt.Sprintf("%d hours %.1f minutes", hours, remainingMinutes) + } +} + +func printTable(headers []string, rows [][]string) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + + for i, header := range headers { + if i > 0 { + _, _ = fmt.Fprint(w, "\t") + } + _, _ = fmt.Fprint(w, header) + } + _, _ = fmt.Fprintln(w) + + for _, row := range rows { + for i, cell := range row { + if i > 0 { + _, _ = fmt.Fprint(w, "\t") + } + _, _ = fmt.Fprint(w, cell) + } + _, _ = fmt.Fprintln(w) + } + + _ = w.Flush() +} + +func analyzeSingle(availability float64, focusPeriod string) error { + calc, err := NewAvailabilityCalculator(availability) + if err != nil { + return err + } + + var periods []Period + if focusPeriod != "" { + periods = []Period{Period(focusPeriod)} + } else { + periods = periodOrder + } + + headers := []string{"Time Period", "Maximum Downtime"} + var rows [][]string + + caser := cases.Title(language.English) + for _, period := range periods { + downtime := calc.CalculateDowntime(period) + rows = append(rows, []string{ + caser.String(string(period)), + downtime.Formatted, + }) + } + + printTable(headers, rows) + return nil +} + +func compareAvailabilities(availability1, availability2 float64) error { + if availability1 == availability2 { + return fmt.Errorf("cannot compare identical availability values") + } + + calc1, err := NewAvailabilityCalculator(availability1) + if err != nil { + return err + } + + calc2, err := NewAvailabilityCalculator(availability2) + if err != nil { + return err + } + + improvement := ((100 - availability1) - (100 - availability2)) / (100 - availability1) * 100 + fmt.Printf("Downtime reduction: %.1f%%\n\n", improvement) + + headers := []string{ + "Time Period", + fmt.Sprintf("%.2f%%", availability1), + fmt.Sprintf("%.2f%%", availability2), + } + var rows [][]string + + caser := cases.Title(language.English) + for _, period := range periodOrder { + downtime1 := calc1.CalculateDowntime(period) + downtime2 := calc2.CalculateDowntime(period) + rows = append(rows, []string{ + caser.String(string(period)), + downtime1.Formatted, + downtime2.Formatted, + }) + } + + printTable(headers, rows) + return nil +} + +func printUsage() { + fmt.Println("Usage:") + fmt.Println(" pviz analyze <availability> - Analyze a single availability target") + fmt.Println(" pviz analyze <availability> --focus <period> - Focus on a specific time period") + fmt.Println(" pviz compare <availability1> <availability2> - Compare two availability targets") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" pviz analyze 99.99") + fmt.Println(" pviz analyze 99.99 --focus quarter") + fmt.Println(" pviz compare 99.9 99.95") + fmt.Println() + fmt.Println("Valid periods: day, week, month, quarter, year") +} + +func main() { + if len(os.Args) < 2 { + printUsage() + os.Exit(1) + } + + command := os.Args[1] + + switch command { + case "analyze": + if len(os.Args) < 3 { + fmt.Fprintf(os.Stderr, "Error: analyze command requires availability argument\n") + os.Exit(1) + } + + availability, err := strconv.ParseFloat(os.Args[2], 64) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: invalid availability value: %v\n", err) + os.Exit(1) + } + + var focusPeriod string + if len(os.Args) >= 5 && os.Args[3] == "--focus" { + focusPeriod = os.Args[4] + validPeriod := false + for _, p := range periodOrder { + if string(p) == focusPeriod { + validPeriod = true + break + } + } + if !validPeriod { + fmt.Fprintf( + os.Stderr, + "Error: invalid period '%s'. Valid periods: day, week, month, quarter, year\n", + focusPeriod, + ) + os.Exit(1) + } + } + + if err := analyzeSingle(availability, focusPeriod); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + case "compare": + if len(os.Args) < 4 { + fmt.Fprintf(os.Stderr, "Error: compare command requires two availability arguments\n") + os.Exit(1) + } + + availability1, err := strconv.ParseFloat(os.Args[2], 64) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: invalid first availability value: %v\n", err) + os.Exit(1) + } + + availability2, err := strconv.ParseFloat(os.Args[3], 64) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: invalid second availability value: %v\n", err) + os.Exit(1) + } + + if err := compareAvailabilities(availability1, availability2); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + default: + fmt.Fprintf(os.Stderr, "Error: unknown command '%s'\n", command) + printUsage() + os.Exit(1) + } +} diff --git a/cmd/seq-stat/main.go b/cmd/seq-stat/main.go new file mode 100644 index 0000000..75bb0a3 --- /dev/null +++ b/cmd/seq-stat/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +var ticks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'} + +func histogram(sequence []float64) []rune { + if len(sequence) == 0 { + return []rune{} + } + + minVal := sequence[0] + maxVal := sequence[0] + + for _, v := range sequence { + if v < minVal { + minVal = v + } + if v > maxVal { + maxVal = v + } + } + + scale := (maxVal - minVal) * 256 / float64(len(ticks)-1) + if scale < 1 { + scale = 1 + } + + result := make([]rune, len(sequence)) + for i, v := range sequence { + index := int(((v - minVal) * 256) / scale) + if index >= len(ticks) { + index = len(ticks) - 1 + } + result[i] = ticks[index] + } + + return result +} + +func parseNumbers(input string) ([]float64, error) { + fields := strings.Fields(input) + numbers := make([]float64, 0, len(fields)) + + for _, field := range fields { + num, err := strconv.ParseFloat(field, 64) + if err != nil { + return nil, fmt.Errorf("invalid number: %s", field) + } + numbers = append(numbers, num) + } + + return numbers, nil +} + +func readFromStdin() ([]float64, error) { + var numbers []float64 + scanner := bufio.NewScanner(os.Stdin) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + lineNumbers, err := parseNumbers(line) + if err != nil { + return nil, err + } + numbers = append(numbers, lineNumbers...) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading from stdin: %v", err) + } + + return numbers, nil +} + +func main() { + var numbers []float64 + var err error + + if len(os.Args) > 1 { + numbers = make([]float64, 0, len(os.Args)-1) + for _, arg := range os.Args[1:] { + num, parseErr := strconv.ParseFloat(arg, 64) + if parseErr != nil { + fmt.Fprintf(os.Stderr, "Invalid number: %s\n", arg) + os.Exit(1) + } + numbers = append(numbers, num) + } + } else { + numbers, err = readFromStdin() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + if len(numbers) == 0 { + fmt.Fprintln(os.Stderr, "No numbers provided") + os.Exit(1) + } + } + + h := histogram(numbers) + fmt.Println(string(h)) +} |
