Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

services/luci/internal/web/steps.go

Raw
package web

import (
	"strconv"
	"strings"
)

// Step is one parsed [luci] step marker pair from a child log.
type Step struct {
	N, Total int
	Command  string
	Seconds  int64
	Done     bool
	Failed   bool
}

const stepPrefix = "[luci] step "

// ParseSteps extracts step markers with their wall time from a log. A step
// without an end marker reports Done=false (still running or cut short).
func ParseSteps(log string) []Step {
	var steps []Step
	byIndex := map[int]int{}
	for _, line := range strings.Split(log, "\n") {
		line = strings.TrimRight(line, "\r")
		if !strings.HasPrefix(line, stepPrefix) {
			continue
		}
		rest := line[len(stepPrefix):]
		slash := strings.Index(rest, "/")
		if slash < 1 {
			continue
		}
		n, err := strconv.Atoi(rest[:slash])
		if err != nil {
			continue
		}
		rest = rest[slash+1:]
		if total, command, isStart := stepStart(rest); isStart {
			if _, seen := byIndex[n]; seen {
				continue
			}
			byIndex[n] = len(steps)
			steps = append(steps, Step{N: n, Total: total, Command: command})
			continue
		}
		if seconds, failed, ended := stepEnd(rest); ended {
			if at, ok := byIndex[n]; ok {
				steps[at].Seconds, steps[at].Done, steps[at].Failed = seconds, true, failed
			}
		}
	}
	return steps
}

func stepStart(rest string) (int, string, bool) {
	colon := strings.Index(rest, ": ")
	if colon < 1 {
		return 0, "", false
	}
	total, err := strconv.Atoi(rest[:colon])
	if err != nil {
		return 0, "", false
	}
	return total, rest[colon+2:], true
}

func stepEnd(rest string) (seconds int64, failed, ended bool) {
	for _, form := range []struct {
		marker string
		failed bool
	}{
		{" done in ", false},
		{" failed after ", true},
	} {
		if i := strings.Index(rest, form.marker); i >= 0 {
			tail := rest[i+len(form.marker):]
			end := strings.IndexByte(tail, 's')
			if end < 1 {
				return 0, form.failed, true
			}
			value, err := strconv.ParseInt(tail[:end], 10, 64)
			if err != nil {
				return 0, form.failed, true
			}
			return value, form.failed, true
		}
	}
	return 0, false, false
}