90 lines
2.1 KiB
Go
90 lines
2.1 KiB
Go
// Package debugbundle owns explicitly requested per-run debug bundles.
|
|
package debugbundle
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const maxCreateAttempts = 16
|
|
|
|
var utcNow = func() time.Time { return time.Now().UTC() }
|
|
|
|
type Bundle struct {
|
|
path, summaryRoot, traceRoot string
|
|
createdAt time.Time
|
|
}
|
|
|
|
func Allocate(parent string) (*Bundle, error) {
|
|
parent = strings.TrimSpace(parent)
|
|
if parent == "" {
|
|
return nil, fmt.Errorf("debug parent must not be empty")
|
|
}
|
|
if err := os.MkdirAll(parent, 0o700); err != nil {
|
|
return nil, fmt.Errorf("create debug parent %q: %w", parent, err)
|
|
}
|
|
var last string
|
|
for attempt := 0; attempt < maxCreateAttempts; attempt++ {
|
|
createdAt := utcNow()
|
|
runID := fmt.Sprintf("run-%d", createdAt.UnixNano())
|
|
path := filepath.Join(parent, runID)
|
|
last = path
|
|
if err := os.Mkdir(path, 0o700); err != nil {
|
|
if os.IsExist(err) {
|
|
continue
|
|
}
|
|
return nil, fmt.Errorf("create debug bundle %q: %w", path, err)
|
|
}
|
|
summary, trace := filepath.Join(path, "summary"), filepath.Join(path, "trace")
|
|
if err := os.Mkdir(summary, 0o700); err != nil {
|
|
_ = os.Remove(path)
|
|
return nil, fmt.Errorf("create debug summary %q: %w", summary, err)
|
|
}
|
|
if err := os.Mkdir(trace, 0o700); err != nil {
|
|
_ = os.RemoveAll(path)
|
|
return nil, fmt.Errorf("create debug trace %q: %w", trace, err)
|
|
}
|
|
return &Bundle{path: path, summaryRoot: summary, traceRoot: trace, createdAt: createdAt}, nil
|
|
}
|
|
return nil, fmt.Errorf("create debug bundle %q: exhausted unique run ID attempts", last)
|
|
}
|
|
func (b *Bundle) Path() string {
|
|
if b == nil {
|
|
return ""
|
|
}
|
|
return b.path
|
|
}
|
|
func (b *Bundle) SummaryRoot() string {
|
|
if b == nil {
|
|
return ""
|
|
}
|
|
return b.summaryRoot
|
|
}
|
|
func (b *Bundle) TraceRoot() string {
|
|
if b == nil {
|
|
return ""
|
|
}
|
|
return b.traceRoot
|
|
}
|
|
func (b *Bundle) RunID() string {
|
|
if b == nil {
|
|
return ""
|
|
}
|
|
return filepath.Base(b.path)
|
|
}
|
|
func (b *Bundle) CreatedAt() time.Time {
|
|
if b == nil {
|
|
return time.Time{}
|
|
}
|
|
return b.createdAt
|
|
}
|
|
func (b *Bundle) Summary() *SummaryWriter {
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
return &SummaryWriter{root: b.summaryRoot, runID: b.RunID(), createdAt: b.createdAt}
|
|
}
|