Skip to content

Commit

Permalink
Enhance ParseDuration with configurable parsing options
Browse files Browse the repository at this point in the history
Introduce ParseDuration() as the primary and simplest entrypoint for
parsing durations. By default it operates in "single-unit mode",
meaning only one unit type is allowed per input string (e.g., "1d" as
opposed to permitting "1d3h5m"). Assumes milliseconds as the default
unit when none is specified.
  • Loading branch information
frobware committed Aug 21, 2023
1 parent 298a36d commit 72f5468
Show file tree
Hide file tree
Showing 5 changed files with 1,115 additions and 321 deletions.
34 changes: 0 additions & 34 deletions cmd/haproxy-timeout-checker/haproxy-timeout-checker.go

This file was deleted.

187 changes: 187 additions & 0 deletions cmd/haproxytimeout/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package main

import (
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"time"

"github.com/frobware/haproxytime"
)

func printErrorWithPosition(input string, err error, position int) {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, input)
fmt.Fprintf(os.Stderr, "%"+fmt.Sprint(position)+"s", "")
fmt.Fprintln(os.Stderr, "^")
}

func formatDuration(d time.Duration) string {
const (
Day = time.Hour * 24
Month = Day * 30
Year = Day * 365
Microsecond = time.Microsecond
)

years := d / Year
d -= years * Year

months := d / Month
d -= months * Month

days := d / Day
d -= days * Day

hours := d / time.Hour
d -= hours * time.Hour

minutes := d / time.Minute
d -= minutes * time.Minute

seconds := d / time.Second
d -= seconds * time.Second

milliseconds := d / time.Millisecond
d -= milliseconds * time.Millisecond

microseconds := d / Microsecond

var result string
if years > 0 {
result += fmt.Sprintf("%dy", years)
}
if months > 0 {
result += fmt.Sprintf("%dmo", months)
}
if days > 0 {
result += fmt.Sprintf("%dd", days)
}
if hours > 0 {
result += fmt.Sprintf("%dh", hours)
}
if minutes > 0 {
result += fmt.Sprintf("%dm", minutes)
}
if seconds > 0 {
result += fmt.Sprintf("%ds", seconds)
}
if milliseconds > 0 {
result += fmt.Sprintf("%dms", milliseconds)
}
if microseconds > 0 {
result += fmt.Sprintf("%dus", microseconds)
}

return result
}

func main() {
max := flag.Bool("m", false, "Print the maximum duration HAProxy can tolerate and exit.")
jsonOutput := flag.Bool("j", false, "Print results in JSON format.")
flag.Parse()

args := flag.Args()

if len(args) == 0 && !*max {
fmt.Println(`usage: haproxytimeout [-m] [-j] <duration>
-m: Print the maximum duration HAProxy can tolerate and exit.
-j: Print results in JSON format. (e.g., haproxytimeout -j <duration> | jq .milliseconds
Input Assumptions:
If the input is a plain number (or a decimal without a unit), it's
assumed to be in milliseconds. For example: 'haproxytimeout 1500'
is assumed to be '1500ms'.
Output Format:
The output maps the human-readable format to the milliseconds
format. For example: 'haproxytimeout 2h30m' will output '2h30m ->
9000000ms'. The right-hand side is always in milliseconds, making it
suitable for direct use in a haproxy.cfg file.
Available units:
d days
h: hours
m: minutes
s: seconds
ms: milliseconds
us: microseconds
Example usage:
haproxytimeout 2h30m -> Convert 2 hours 30 minutes to milliseconds.
haproxytimeout 1500ms -> Convert 1500 milliseconds to a human-readable format.
haproxytimeout -j 1h17m -> Get JSON output for 1h 17minutes
haproxytimeout -m -> Show the maximum duration HAProxy can tolerate.`)
os.Exit(1)
}

type outputResult struct {
HumanReadable string `json:"human_readable"`
Milliseconds int64 `json:"milliseconds"`
}

// Output function based on jsonOutput flag.
output := func(human string, millis int64) {
if human == "" {
human = formatDuration(time.Duration(millis) * time.Millisecond)
}

if *jsonOutput {
res := outputResult{
HumanReadable: human,
Milliseconds: millis,
}
jsonRes, err := json.Marshal(res)
if err != nil {
fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err)
os.Exit(6)
}
fmt.Println(string(jsonRes))
} else {
fmt.Printf("%s -> %dms\n", human, millis)
}
}

// Handle max duration request.
if *max {
maxDuration := haproxytime.MaxTimeout.Milliseconds()
// Always format max duration as human-readable.
output(formatDuration(haproxytime.MaxTimeout), maxDuration)
return
}

parserCfg, err := haproxytime.NewParserConfig(haproxytime.UnitMillisecond, haproxytime.ModeMultiUnit)
if err != nil {
fmt.Fprintf(os.Stderr, "error: failed to create parser configuration: %v\n", err)
os.Exit(2)
}

duration, err := haproxytime.ParseDurationWithConfig(args[0], parserCfg)
if err != nil {
var se *haproxytime.SyntaxError
var oe *haproxytime.OverflowError
switch {
case errors.As(err, &se):
printErrorWithPosition(args[0], err, se.Position)
os.Exit(3)
case errors.As(err, &oe):
printErrorWithPosition(args[0], err, oe.Position)
os.Exit(4)
default:
panic(err)
}
}

if haproxytime.DurationExceedsMaxTimeout(duration) {
maxHumanFormat := formatDuration(haproxytime.MaxTimeout)
humanFormat := formatDuration(time.Duration(duration.Milliseconds()) * time.Millisecond)
fmt.Fprintf(os.Stderr, "%s exceeds HAProxy's maximum duration %s (%vms)\n",
humanFormat, maxHumanFormat, haproxytime.MaxTimeout.Milliseconds())
os.Exit(5)
}

output(formatDuration(duration), duration.Milliseconds())
}
Loading

0 comments on commit 72f5468

Please sign in to comment.