aboutsummaryrefslogtreecommitdiff
path: root/cmd/pviz
diff options
context:
space:
mode:
authorFranck Cuny <franck@fcuny.net>2025-08-17 10:14:22 -0700
committerFranck Cuny <franck@fcuny.net>2025-08-17 10:16:52 -0700
commit881ff8cc72476f95528e402495040ded3b732495 (patch)
tree2c1442acda2feeacc593ba6820ccf6888a8f2277 /cmd/pviz
downloadx-881ff8cc72476f95528e402495040ded3b732495.tar.gz
first command added: pviz
`pviz` is a command-line tool that helps you understand the real impact of service availability targets (SLAs) by converting availability percentages into actual downtime durations across different time periods.
Diffstat (limited to 'cmd/pviz')
-rw-r--r--cmd/pviz/main.go258
1 files changed, 258 insertions, 0 deletions
diff --git a/cmd/pviz/main.go b/cmd/pviz/main.go
new file mode 100644
index 0000000..58040be
--- /dev/null
+++ b/cmd/pviz/main.go
@@ -0,0 +1,258 @@
+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)
+ }
+}