Skip to content

Commit

Permalink
Add -readjson and -jsontemplate, rename Start to StartText, add JSONT…
Browse files Browse the repository at this point in the history
…ext, Object, and StartObject.
  • Loading branch information
sgreben committed Jan 25, 2018
1 parent 6189993 commit 08bc81e
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = 2.0.2
VERSION = 3.0.0

PACKAGES := $(shell go list -f {{.Dir}} ./...)
GOFILES := $(addsuffix /*.go,$(PACKAGES))
Expand Down
57 changes: 49 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ Or [download the binary](https://github.com/sgreben/ts/releases) from the releas

```text
Usage of ts:
-plain
-template='{{.TimeString}} +{{.DeltaNanos}} {{.Text}}'
-start string
a regex pattern. if given, only lines matching it (re)start the stopwatch
-template string
go template (https://golang.org/pkg/text/template)
-timeformat string
either a go time format string or one of the predefined format names (https://golang.org/pkg/time/#pkg-constants)
either a go time format string or one of the predefined format names (https://golang.org/pkg/time/#pkg-constants)
-template string
go template (https://golang.org/pkg/text/template)
-start string
a regex pattern. if given, only lines matching it (re)start the stopwatch
-readjson
parse each stdin line as JSON
-jsontemplate string
go template, used to extract text from json input. implies -readjson
-plain
-template='{{.TimeString}} +{{.DeltaNanos}} {{.Text}}'
```

### JSON output
Expand Down Expand Up @@ -89,6 +93,43 @@ $ (echo Hello; echo World) | ts -template '{{ .I }} {{.TimeSecs}} {{.Text}}'

The fields available to the template are specified in the [`line` struct](cmd/ts/main.go#L15).

### Stopwatch regex

Sometimes you need to measure the duration *between* certain tokens in the input.

To help with this, `ts` can match each line against a regular expression and only reset the stopwatch (`delta`, `deltaSecs`, `deltaNanos`) when a line matches.

The regular expression can be specified via the `-start` parameter.

### JSON input

Using `-readjson`, you can tell `ts` to parse each input line as a separate JSON object. Fields of this object can be referred to via `.Object` in the `line` struct, like this:

```bash
$ echo '{"hello": "World"}' | ts -readjson -template "{{.TimeString}} {{.Object.hello}}"
2018-01-25T21:55:06+01:00 World
```

Additionally, you can also specify a template `-jsontemplate` to extract text from the object. The output of this template is matched against the stopwatch regex.

This allows you to use only specific fields of the object as stopwatch reset triggers. For example:

```bash
$ (echo {}; sleep 1; echo {}; sleep 1; echo '{"reset": "yes"}'; echo {}) |
ts -jsontemplate "{{.reset}}" -start yes -template "{{.I}} {{.DeltaNanos}}"
0 14374
1 1005916918
2 2017292187
3 79099
```

The output of the JSON template is stored in the field `.JSONText` of the `line` struct:

```bash
$ echo '{"message":"hello"}' | ts -jsontemplate "{{.message}}" -template "{{.TimeString}} {{.JSONText}}"
2018-01-25T22:20:59+01:00 hello
```

## Example

Finding the slowest step in a `docker build` (using `jq`):
Expand All @@ -104,7 +145,7 @@ RUN echo Done being slow
```bash
docker build . |
ts -start ^Step |
jq -s 'max_by(.deltaNanos) | {step:.start, duration:.delta}'
jq -s 'max_by(.deltaNanos) | {step:.startText, duration:.delta}'
```

```json
Expand Down
48 changes: 39 additions & 9 deletions cmd/ts/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,20 @@ type line struct {
TotalString string `json:"total,omitempty"`
Total time.Duration `json:"-"`
Text string `json:"text,omitempty"`
Start string `json:"start,omitempty"`
StartText string `json:"startText,omitempty"`
JSONText string `json:"jsonText,omitempty"`
Object interface{} `json:"object,omitempty"`
StartObject interface{} `json:"startObject,omitempty"`
}

type configuration struct {
timeFormat string // -timeformat="..."
template string // -template="..."
plain bool // -plain
start string // -start="..."
version string
timeFormat string // -timeformat="..."
template string // -template="..."
plain bool // -plain
start string // -start="..."
readJSON bool // -readjson
jsonTemplate string // -jsontemplate="..."
version string
}

var config = configuration{}
Expand Down Expand Up @@ -70,7 +75,7 @@ func jsonPrinter() printerFunc {
}

func templatePrinter(t string) printerFunc {
template := template.Must(template.New("").Parse(t))
template := template.Must(template.New("-template").Option("missingkey=zero").Parse(t))
newline := []byte("\n")
return func(line *line) error {
err := template.Execute(os.Stdout, line)
Expand All @@ -89,12 +94,15 @@ func timeFormatsHelp() string {
}

var start *regexp.Regexp
var jsonTemplate *template.Template

func init() {
flag.StringVar(&config.template, "template", "", "go template (https://golang.org/pkg/text/template)")
flag.StringVar(&config.timeFormat, "timeformat", "RFC3339", timeFormatsHelp())
flag.BoolVar(&config.plain, "plain", false, "-template='{{.TimeString}} +{{.DeltaNanos}} {{.Text}}'")
flag.StringVar(&config.start, "start", "", "a regex pattern. if given, only lines matching it (re)start the stopwatch")
flag.BoolVar(&config.readJSON, "readjson", false, "parse each stdin line as JSON")
flag.StringVar(&config.jsonTemplate, "jsontemplate", "", "go template, used to extract text from json input. implies -readjson")
flag.Parse()
if knownFormat, ok := timeFormats[config.timeFormat]; ok {
config.timeFormat = knownFormat
Expand All @@ -110,6 +118,13 @@ func init() {
if config.start != "" {
start = regexp.MustCompile(config.start)
}
if config.readJSON && config.jsonTemplate == "" {
config.jsonTemplate = "{{.}}"
}
if config.jsonTemplate != "" {
config.readJSON = true
jsonTemplate = template.Must(template.New("-jsontemplate").Option("missingkey=zero").Parse(config.jsonTemplate))
}
}

func main() {
Expand All @@ -118,6 +133,7 @@ func main() {
first := line.Time
last := line.Time
i := uint64(0)
b := bytes.NewBuffer(nil)
for scanner.Scan() {
now := time.Now()
delta := now.Sub(last)
Expand All @@ -136,13 +152,27 @@ func main() {
line.Time = now
line.Text = scanner.Text()
line.I = i
match := line.Text
if config.readJSON {
b.Reset()
line.Object = new(interface{})
if err := json.Unmarshal([]byte(line.Text), &line.Object); err != nil {
fmt.Fprintln(os.Stderr, "parse error:", err)
}
if err := jsonTemplate.Execute(b, line.Object); err != nil {
fmt.Fprintln(os.Stderr, "template error:", err)
}
line.JSONText = b.String()
match = line.JSONText
}
if err := printer(&line); err != nil {
fmt.Fprintln(os.Stderr, "output error:", err)
}
if start != nil {
if start.MatchString(line.Text) {
if start.MatchString(match) {
last = now
line.Start = line.Text
line.StartText = line.Text
line.StartObject = line.Object
}
} else {
last = now
Expand Down

0 comments on commit 08bc81e

Please sign in to comment.