Skip to content

Commit

Permalink
all: add state routes and build info
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjan committed Aug 11, 2023
1 parent a760a08 commit e1acdff
Show file tree
Hide file tree
Showing 18 changed files with 388 additions and 38 deletions.
9 changes: 9 additions & 0 deletions api/autopilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ type (
UptimeMS ParamDuration `json:"uptimeMS"`
}

// AutopilotStateResponse is the response type for the /autopilot/state
// endpoint.
AutopilotStateResponse struct {
AutopilotStatusResponse // TODO: deprecate /autopilot/status

StartTime time.Time `json:"startTime"`
BuildState
}

HostHandlerResponseChecks struct {
Gouging bool `json:"gouging"`
GougingBreakdown HostGougingBreakdown `json:"gougingBreakdown"`
Expand Down
6 changes: 6 additions & 0 deletions api/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ type AccountHandlerPOST struct {
HostKey types.PublicKey `json:"hostKey"`
}

// BusStateResponse is the response type for the /bus/state endpoint.
type BusStateResponse struct {
StartTime time.Time `json:"startTime"`
BuildState
}

// ConsensusState holds the current blockheight and whether we are synced or not.
type ConsensusState struct {
BlockHeight uint64 `json:"blockHeight"`
Expand Down
14 changes: 14 additions & 0 deletions api/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package api

import "time"

type (
// BuildState contains static information about the build.
BuildState struct {
Network string `json:"network"`
Version string `json:"version"`
Commit string `json:"commit"`
OS string `json:"OS"`
BuildTime time.Time `json:"buildTime"`
}
)
7 changes: 7 additions & 0 deletions api/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ type UploaderStats struct {
AvgSectorUploadSpeedMBPS float64 `json:"avgSectorUploadSpeedMBPS"`
}

// WorkerStateResponse is the response type for the /worker/state endpoint.
type WorkerStateResponse struct {
ID string `json:"id"`
StartTime time.Time `json:"startTime"`
BuildState
}

// An UploadOption overrides an option on the upload and migrate endpoints in
// the worker.
type UploadOption func(url.Values)
Expand Down
56 changes: 47 additions & 9 deletions autopilot/autopilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"runtime"
"strings"
"sync"
"time"
Expand All @@ -16,6 +17,7 @@ import (
"go.sia.tech/jape"
"go.sia.tech/renterd/alerts"
"go.sia.tech/renterd/api"
"go.sia.tech/renterd/build"
"go.sia.tech/renterd/hostdb"
"go.sia.tech/renterd/object"
"go.sia.tech/renterd/tracing"
Expand Down Expand Up @@ -108,11 +110,11 @@ type Autopilot struct {
tickerDuration time.Duration
wg sync.WaitGroup

startStopMu sync.Mutex
runningSince time.Time
stopChan chan struct{}
ticker *time.Ticker
triggerChan chan bool
startStopMu sync.Mutex
startTime time.Time
stopChan chan struct{}
ticker *time.Ticker
triggerChan chan bool
}

// state holds a bunch of variables that are used by the autopilot and updated
Expand Down Expand Up @@ -169,6 +171,7 @@ func (ap *Autopilot) Handler() http.Handler {
"POST /hosts": ap.hostsHandlerPOST,
"GET /host/:hostKey": ap.hostHandlerGET,
"GET /status": ap.statusHandlerGET,
"GET /state": ap.stateHandlerGET,
}))
}

Expand All @@ -178,7 +181,7 @@ func (ap *Autopilot) Run() error {
ap.startStopMu.Unlock()
return errors.New("already running")
}
ap.runningSince = time.Now()
ap.startTime = time.Now()
ap.stopChan = make(chan struct{})
ap.triggerChan = make(chan bool)
ap.ticker = time.NewTicker(ap.tickerDuration)
Expand Down Expand Up @@ -294,7 +297,7 @@ func (ap *Autopilot) Shutdown(_ context.Context) error {
close(ap.stopChan)
close(ap.triggerChan)
ap.wg.Wait()
ap.runningSince = time.Time{}
ap.startTime = time.Time{}
}
return nil
}
Expand All @@ -317,11 +320,17 @@ func (ap *Autopilot) Trigger(forceScan bool) bool {
}
}

func (ap *Autopilot) StartTime() time.Time {
ap.startStopMu.Lock()
defer ap.startStopMu.Unlock()
return ap.startTime
}

func (ap *Autopilot) Uptime() (dur time.Duration) {
ap.startStopMu.Lock()
defer ap.startStopMu.Unlock()
if ap.isRunning() {
dur = time.Since(ap.runningSince)
dur = time.Since(ap.startTime)
}
return
}
Expand Down Expand Up @@ -387,7 +396,7 @@ func (ap *Autopilot) blockUntilSynced(interrupt <-chan time.Time) bool {
}

func (ap *Autopilot) isRunning() bool {
return !ap.runningSince.IsZero()
return !ap.startTime.IsZero()
}

func (ap *Autopilot) updateState(ctx context.Context) error {
Expand Down Expand Up @@ -599,6 +608,35 @@ func (ap *Autopilot) statusHandlerGET(jc jape.Context) {
})
}

func (ap *Autopilot) stateHandlerGET(jc jape.Context) {
migrating, mLastStart := ap.m.Status()
scanning, sLastStart := ap.s.Status()
_, err := ap.bus.Autopilot(jc.Request.Context(), ap.id)
if err != nil && !strings.Contains(err.Error(), api.ErrAutopilotNotFound.Error()) {
jc.Error(err, http.StatusInternalServerError)
return
}

jc.Encode(api.AutopilotStateResponse{
AutopilotStatusResponse: api.AutopilotStatusResponse{
Configured: err == nil,
Migrating: migrating,
MigratingLastStart: api.ParamTime(mLastStart),
Scanning: scanning,
ScanningLastStart: api.ParamTime(sLastStart),
UptimeMS: api.ParamDuration(ap.Uptime()),
},
StartTime: ap.StartTime(),
BuildState: api.BuildState{
Network: build.ConsensusNetworkName,
Version: build.Version(),
Commit: build.Commit(),
OS: runtime.GOOS,
BuildTime: build.BuildTime(),
},
})
}

func (ap *Autopilot) hostsHandlerPOST(jc jape.Context) {
var req api.SearchHostsRequest
if jc.Decode(&req) != nil {
Expand Down
6 changes: 6 additions & 0 deletions autopilot/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ func (c *Client) HostInfos(ctx context.Context, filterMode, usabilityMode string
return
}

// State returns the current state of the autopilot.
func (c *Client) State() (state api.AutopilotStateResponse, err error) {
err = c.c.GET("/state", &state)
return
}

func (c *Client) Status() (resp api.AutopilotStatusResponse, err error) {
err = c.c.GET("/status", &resp)
return
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion build/env_testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

const (
ConsensusNetworkName = "Testnet-Zen"
ConsensusNetworkName = "Zen Testnet"
DefaultAPIAddress = "localhost:9880"
DefaultGatewayAddress = ":9881"
)
Expand Down
134 changes: 134 additions & 0 deletions build/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//go:build ignore

// This script generates meta.go which contains version info for the hostd binary. It can be run with `go generate`.
//
//go:generate -command go run gen.go
package main

import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"os/exec"
"strings"
"text/template"
"time"
)

const logFormat = `{%n "commit": "%H",%n "shortCommit": "%h",%n "timestamp": "%cD",%n "tag": "%(describe:tags=true)"%n}`

type (
gitTime time.Time

gitMeta struct {
Commit string `json:"commit"`
ShortCommit string `json:"shortCommit"`
Timestamp gitTime `json:"timestamp"`
Tag string `json:"tag"`
}
)

var buildTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT.
// This file was generated by go generate at {{ .RunTime }}.
package build
import (
"time"
)
const (
commit = "{{ .Commit }}"
version = "{{ .Version }}"
buildTime = {{ .UnixTimestamp }}
)
// Commit returns the commit hash of hostd
func Commit() string {
return commit
}
// Version returns the version of hostd
func Version() string {
return version
}
// BuildTime returns the time at which the binary was built.
func BuildTime() time.Time {
return time.Unix(buildTime, 0)
}`))

// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *gitTime) UnmarshalJSON(buf []byte) error {
timeFormats := []string{
time.RFC1123Z,
"Mon, 2 Jan 2006 15:04:05 -0700",
"2006-01-02 15:04:05 -0700",
time.UnixDate,
time.ANSIC,
time.RFC3339,
time.RFC1123,
}

for _, format := range timeFormats {
parsed, err := time.Parse(format, strings.Trim(string(buf), `"`))
if err == nil {
*t = gitTime(parsed)
return nil
}
}
return errors.New("failed to parse time")
}

func getGitMeta() (meta gitMeta, _ error) {
cmd := exec.Command("git", "log", "-1", "--pretty=format:"+logFormat+"")
buf, err := cmd.Output()
if err != nil {
if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 {
return gitMeta{}, fmt.Errorf("command failed: %w", errors.New(string(err.Stderr)))
}
return gitMeta{}, fmt.Errorf("failed to execute command: %w", err)
} else if err := json.Unmarshal(buf, &meta); err != nil {
return gitMeta{}, fmt.Errorf("failed to unmarshal json: %w", err)
}
return
}

func main() {
meta, err := getGitMeta()
if err != nil {
log.Fatalln(err)
}

commit := meta.ShortCommit
version := meta.Tag
if len(version) == 0 {
// no version, use commit and current time for development
version = commit
meta.Timestamp = gitTime(time.Now())
}

f, err := os.Create("meta.go")
if err != nil {
log.Fatalln(err)
}
defer f.Close()

err = buildTemplate.Execute(f, struct {
Commit string
Version string
UnixTimestamp int64

RunTime string
}{
Commit: commit,
Version: version,
UnixTimestamp: time.Time(meta.Timestamp).Unix(),

RunTime: time.Now().Format(time.RFC3339),
})
if err != nil {
log.Fatalln(err)
}
}
28 changes: 28 additions & 0 deletions build/meta.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e1acdff

Please sign in to comment.