diff --git a/curiosrc/web/hapi/routes.go b/curiosrc/web/hapi/routes.go
index fd2f24e9434..430b02efb94 100644
--- a/curiosrc/web/hapi/routes.go
+++ b/curiosrc/web/hapi/routes.go
@@ -2,12 +2,13 @@ package hapi
import (
"embed"
- "html/template"
+ "text/template"
"github.com/gorilla/mux"
logging "github.com/ipfs/go-log/v2"
"golang.org/x/xerrors"
+ "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/cmd/curio/deps"
)
@@ -15,7 +16,7 @@ import (
var templateFS embed.FS
func Routes(r *mux.Router, deps *deps.Deps) error {
- t, err := template.ParseFS(templateFS, "web/*")
+ t, err := makeTemplate().ParseFS(templateFS, "web/*")
if err != nil {
return xerrors.Errorf("parse templates: %w", err)
}
@@ -28,6 +29,7 @@ func Routes(r *mux.Router, deps *deps.Deps) error {
go a.watchRpc()
go a.watchActor()
+ // index page (simple info)
r.HandleFunc("/simpleinfo/actorsummary", a.actorSummary)
r.HandleFunc("/simpleinfo/machines", a.indexMachines)
r.HandleFunc("/simpleinfo/tasks", a.indexTasks)
@@ -35,8 +37,19 @@ func Routes(r *mux.Router, deps *deps.Deps) error {
r.HandleFunc("/simpleinfo/pipeline-porep", a.indexPipelinePorep)
// pipeline-porep page
- r.HandleFunc("/simpleinfo/pipeline-porep/sectors", a.pipelinePorepSectors)
+ r.HandleFunc("/pipeline-porep/sectors", a.pipelinePorepSectors)
+
+ // node info page
+ r.HandleFunc("/node/{id}", a.nodeInfo)
return nil
}
+func makeTemplate() *template.Template {
+ return template.New("").Funcs(template.FuncMap{
+ "toHumanBytes": func(b int64) string {
+ return types.SizeStr(types.NewInt(uint64(b)))
+ },
+ })
+}
+
var log = logging.Logger("curio/web")
diff --git a/curiosrc/web/hapi/simpleinfo.go b/curiosrc/web/hapi/simpleinfo.go
index fd6635059f2..014f154f8e8 100644
--- a/curiosrc/web/hapi/simpleinfo.go
+++ b/curiosrc/web/hapi/simpleinfo.go
@@ -1,14 +1,18 @@
package hapi
import (
+ "bytes"
"context"
- "html/template"
+ "fmt"
"net/http"
"os"
"sort"
+ "strconv"
"sync"
+ "text/template"
"time"
+ "github.com/gorilla/mux"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api/v1api"
@@ -99,12 +103,37 @@ func (a *app) indexPipelinePorep(w http.ResponseWriter, r *http.Request) {
a.executeTemplate(w, "pipeline_porep", s)
}
+func (a *app) nodeInfo(writer http.ResponseWriter, request *http.Request) {
+ params := mux.Vars(request)
+
+ id, ok := params["id"]
+ if !ok {
+ http.Error(writer, "missing id", http.StatusBadRequest)
+ return
+ }
+
+ intid, err := strconv.ParseInt(id, 10, 64)
+ if err != nil {
+ http.Error(writer, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ mi, err := a.clusterNodeInfo(request.Context(), intid)
+ if err != nil {
+ log.Errorf("machine info: %v", err)
+ http.Error(writer, "internal server error", http.StatusInternalServerError)
+ return
+ }
+
+ a.executePageTemplate(writer, "node_info", "Node Info", mi)
+}
+
var templateDev = os.Getenv("LOTUS_WEB_DEV") == "1"
func (a *app) executeTemplate(w http.ResponseWriter, name string, data interface{}) {
if templateDev {
- fs := os.DirFS("./cmd/curio/web/hapi/web")
- a.t = template.Must(template.ParseFS(fs, "*"))
+ fs := os.DirFS("./curiosrc/web/hapi/web")
+ a.t = template.Must(makeTemplate().ParseFS(fs, "*"))
}
if err := a.t.ExecuteTemplate(w, name, data); err != nil {
log.Errorf("execute template %s: %v", name, err)
@@ -112,6 +141,22 @@ func (a *app) executeTemplate(w http.ResponseWriter, name string, data interface
}
}
+func (a *app) executePageTemplate(w http.ResponseWriter, name, title string, data interface{}) {
+ if templateDev {
+ fs := os.DirFS("./curiosrc/web/hapi/web")
+ a.t = template.Must(makeTemplate().ParseFS(fs, "*"))
+ }
+ var contentBuf bytes.Buffer
+ if err := a.t.ExecuteTemplate(&contentBuf, name, data); err != nil {
+ log.Errorf("execute template %s: %v", name, err)
+ http.Error(w, "internal server error", http.StatusInternalServerError)
+ }
+ a.executeTemplate(w, "root", map[string]interface{}{
+ "PageTitle": title,
+ "Content": contentBuf.String(),
+ })
+}
+
type machineRecentTask struct {
TaskName string
Success int64
@@ -127,10 +172,10 @@ type machineSummary struct {
}
type taskSummary struct {
- Name string
- SincePosted string
- Owner *string
- ID int64
+ Name string
+ SincePosted string
+ Owner, OwnerID *string
+ ID int64
}
type taskHistorySummary struct {
@@ -219,7 +264,7 @@ func (a *app) clusterMachineSummary(ctx context.Context) ([]machineSummary, erro
}
func (a *app) clusterTaskSummary(ctx context.Context) ([]taskSummary, error) {
- rows, err := a.db.Query(ctx, "SELECT id, name, update_time, owner_id FROM harmony_task order by update_time asc, owner_id")
+ rows, err := a.db.Query(ctx, "SELECT t.id, t.name, t.update_time, t.owner_id, hm.host_and_port FROM harmony_task t LEFT JOIN curio.harmony_machines hm ON hm.id = t.owner_id ORDER BY t.update_time ASC, t.owner_id")
if err != nil {
return nil, err // Handle error
}
@@ -230,7 +275,7 @@ func (a *app) clusterTaskSummary(ctx context.Context) ([]taskSummary, error) {
var t taskSummary
var posted time.Time
- if err := rows.Scan(&t.ID, &t.Name, &posted, &t.Owner); err != nil {
+ if err := rows.Scan(&t.ID, &t.Name, &posted, &t.OwnerID, &t.Owner); err != nil {
return nil, err // Handle error
}
@@ -321,3 +366,232 @@ func (a *app) porepPipelineSummary(ctx context.Context) ([]porepPipelineSummary,
}
return summaries, nil
}
+
+type machineInfo struct {
+ Info struct {
+ Host string
+ ID int64
+ LastContact string
+ CPU int64
+ Memory int64
+ GPU int64
+ }
+
+ // Storage
+ Storage []struct {
+ ID string
+ Weight int64
+ MaxStorage int64
+ CanSeal bool
+ CanStore bool
+ Groups string
+ AllowTo string
+ AllowTypes string
+ DenyTypes string
+ Capacity int64
+ Available int64
+ FSAvailable int64
+ Reserved int64
+ Used int64
+ LastHeartbeat time.Time
+ HeartbeatErr *string
+
+ UsedPercent float64
+ ReservedPercent float64
+ }
+
+ /*TotalStorage struct {
+ MaxStorage int64
+ UsedStorage int64
+
+ MaxSealStorage int64
+ UsedSealStorage int64
+
+ MaxStoreStorage int64
+ UsedStoreStorage int64
+ }*/
+
+ // Tasks
+ RunningTasks []struct {
+ ID int64
+ Task string
+ Posted string
+
+ PoRepSector, PoRepSectorSP *int64
+ }
+
+ FinishedTasks []struct {
+ ID int64
+ Task string
+ Posted string
+ Start string
+ Queued string
+ Took string
+ Outcome string
+ Message string
+ }
+}
+
+func (a *app) clusterNodeInfo(ctx context.Context, id int64) (*machineInfo, error) {
+ rows, err := a.db.Query(ctx, "SELECT id, host_and_port, last_contact, cpu, ram, gpu FROM harmony_machines WHERE id=$1 ORDER BY host_and_port ASC", id)
+ if err != nil {
+ return nil, err // Handle error
+ }
+ defer rows.Close()
+
+ var summaries []machineInfo
+ if rows.Next() {
+ var m machineInfo
+ var lastContact time.Time
+
+ if err := rows.Scan(&m.Info.ID, &m.Info.Host, &lastContact, &m.Info.CPU, &m.Info.Memory, &m.Info.GPU); err != nil {
+ return nil, err
+ }
+
+ m.Info.LastContact = time.Since(lastContact).Round(time.Second).String()
+
+ summaries = append(summaries, m)
+ }
+
+ if len(summaries) == 0 {
+ return nil, xerrors.Errorf("machine not found")
+ }
+
+ // query storage info
+ rows2, err := a.db.Query(ctx, "SELECT storage_id, weight, max_storage, can_seal, can_store, groups, allow_to, allow_types, deny_types, capacity, available, fs_available, reserved, used, last_heartbeat, heartbeat_err FROM storage_path WHERE urls LIKE '%' || $1 || '%'", summaries[0].Info.Host)
+ if err != nil {
+ return nil, err
+ }
+
+ defer rows2.Close()
+
+ for rows2.Next() {
+ var s struct {
+ ID string
+ Weight int64
+ MaxStorage int64
+ CanSeal bool
+ CanStore bool
+ Groups string
+ AllowTo string
+ AllowTypes string
+ DenyTypes string
+ Capacity int64
+ Available int64
+ FSAvailable int64
+ Reserved int64
+ Used int64
+ LastHeartbeat time.Time
+ HeartbeatErr *string
+
+ UsedPercent float64
+ ReservedPercent float64
+ }
+ if err := rows2.Scan(&s.ID, &s.Weight, &s.MaxStorage, &s.CanSeal, &s.CanStore, &s.Groups, &s.AllowTo, &s.AllowTypes, &s.DenyTypes, &s.Capacity, &s.Available, &s.FSAvailable, &s.Reserved, &s.Used, &s.LastHeartbeat, &s.HeartbeatErr); err != nil {
+ return nil, err
+ }
+
+ s.UsedPercent = float64(s.Capacity-s.FSAvailable) * 100 / float64(s.Capacity)
+ s.ReservedPercent = float64(s.Capacity-(s.FSAvailable+s.Reserved))*100/float64(s.Capacity) - s.UsedPercent
+
+ summaries[0].Storage = append(summaries[0].Storage, s)
+ }
+
+ // tasks
+ rows3, err := a.db.Query(ctx, "SELECT id, name, posted_time FROM harmony_task WHERE owner_id=$1", summaries[0].Info.ID)
+ if err != nil {
+ return nil, err
+ }
+
+ defer rows3.Close()
+
+ for rows3.Next() {
+ var t struct {
+ ID int64
+ Task string
+ Posted string
+
+ PoRepSector *int64
+ PoRepSectorSP *int64
+ }
+
+ var posted time.Time
+ if err := rows3.Scan(&t.ID, &t.Task, &posted); err != nil {
+ return nil, err
+ }
+ t.Posted = time.Since(posted).Round(time.Second).String()
+
+ {
+ // try to find in the porep pipeline
+ rows4, err := a.db.Query(ctx, `SELECT sp_id, sector_number FROM sectors_sdr_pipeline
+ WHERE task_id_sdr=$1
+ OR task_id_tree_d=$1
+ OR task_id_tree_c=$1
+ OR task_id_tree_r=$1
+ OR task_id_precommit_msg=$1
+ OR task_id_porep=$1
+ OR task_id_commit_msg=$1
+ OR task_id_finalize=$1
+ OR task_id_move_storage=$1
+ `, t.ID)
+ if err != nil {
+ return nil, err
+ }
+
+ if rows4.Next() {
+ var spid int64
+ var sector int64
+ if err := rows4.Scan(&spid, §or); err != nil {
+ return nil, err
+ }
+ t.PoRepSector = §or
+ t.PoRepSectorSP = &spid
+ }
+
+ rows4.Close()
+ }
+
+ summaries[0].RunningTasks = append(summaries[0].RunningTasks, t)
+ }
+
+ rows5, err := a.db.Query(ctx, `SELECT name, task_id, posted, work_start, work_end, result, err FROM harmony_task_history WHERE completed_by_host_and_port = $1 ORDER BY work_end DESC LIMIT 15`, summaries[0].Info.Host)
+ if err != nil {
+ return nil, err
+ }
+ defer rows5.Close()
+
+ for rows5.Next() {
+ var ft struct {
+ ID int64
+ Task string
+ Posted string
+ Start string
+ Queued string
+ Took string
+ Outcome string
+
+ Message string
+ }
+
+ var posted, start, end time.Time
+ var result bool
+ if err := rows5.Scan(&ft.Task, &ft.ID, &posted, &start, &end, &result, &ft.Message); err != nil {
+ return nil, err
+ }
+
+ ft.Outcome = "Success"
+ if !result {
+ ft.Outcome = "Failed"
+ }
+
+ // Format the times and durations
+ ft.Posted = posted.Format("02 Jan 06 15:04 MST")
+ ft.Start = start.Format("02 Jan 06 15:04 MST")
+ ft.Queued = fmt.Sprintf("%s", start.Sub(posted).Round(time.Second).String())
+ ft.Took = fmt.Sprintf("%s", end.Sub(start).Round(time.Second))
+
+ summaries[0].FinishedTasks = append(summaries[0].FinishedTasks, ft)
+ }
+
+ return &summaries[0], nil
+}
diff --git a/curiosrc/web/hapi/web/cluster_machines.gohtml b/curiosrc/web/hapi/web/cluster_machines.gohtml
index b253a05610e..5fb18b52c24 100644
--- a/curiosrc/web/hapi/web/cluster_machines.gohtml
+++ b/curiosrc/web/hapi/web/cluster_machines.gohtml
@@ -1,7 +1,7 @@
{{define "cluster_machines"}}
{{range .}}
- {{.Address}} |
+ {{.Address}} |
{{.ID}} |
{{.SinceContact}} |
{{range .RecentTasks}}
diff --git a/curiosrc/web/hapi/web/cluster_tasks.gohtml b/curiosrc/web/hapi/web/cluster_tasks.gohtml
index 690ab8cff83..b7b3faec0ef 100644
--- a/curiosrc/web/hapi/web/cluster_tasks.gohtml
+++ b/curiosrc/web/hapi/web/cluster_tasks.gohtml
@@ -4,7 +4,7 @@
{{.Name}} |
{{.ID}} |
{{.SincePosted}} |
- {{.Owner}} |
+ {{if ne nil .OwnerID}}{{.Owner}}{{end}} |
{{end}}
{{end}}
diff --git a/curiosrc/web/hapi/web/node_info.gohtml b/curiosrc/web/hapi/web/node_info.gohtml
new file mode 100644
index 00000000000..16f60a47522
--- /dev/null
+++ b/curiosrc/web/hapi/web/node_info.gohtml
@@ -0,0 +1,100 @@
+{{define "node_info"}}
+Info
+
+
+ Host |
+ ID |
+ Last Contact |
+ CPU |
+ Memory |
+ GPU |
+ Debug |
+
+
+ {{.Info.Host}} |
+ {{.Info.ID}} |
+ {{.Info.LastContact}} |
+ {{.Info.CPU}} |
+ {{toHumanBytes .Info.Memory}} |
+ {{.Info.GPU}} |
+ [pprof] |
+
+
+
+Storage
+
+
+ ID |
+ Type |
+ Capacity |
+ Available |
+ Reserved |
+ |
+
+ {{range .Storage}}
+
+ {{.ID}} |
+
+ {{if and (not .CanSeal) (not .CanStore)}}ReadOnly{{end}}
+ {{if and (.CanSeal) (not .CanStore)}}Seal{{end}}
+ {{if and (not .CanSeal) (.CanStore)}}Store{{end}}
+ {{if and (.CanSeal) (.CanStore)}}Seal+Store{{end}}
+ |
+ {{toHumanBytes .Capacity}} |
+ {{toHumanBytes .Available}} |
+ {{toHumanBytes .Reserved}} |
+
+
+ |
+
+ {{end}}
+
+
+
+Tasks
+Running
+
+Recently Finished
+
+
+ ID |
+ Task |
+ Posted |
+ Start |
+ Queued |
+ Took |
+ Outcome |
+ Message |
+
+ {{range .FinishedTasks}}
+
+ {{.ID}} |
+ {{.Task}} |
+ {{.Posted}} |
+ {{.Start}} |
+ {{.Queued}} |
+ {{.Took}} |
+ {{.Outcome}} |
+ {{.Message}} |
+
+ {{end}}
+
+{{end}}
diff --git a/curiosrc/web/hapi/web/root.gohtml b/curiosrc/web/hapi/web/root.gohtml
new file mode 100644
index 00000000000..aa7bc586a51
--- /dev/null
+++ b/curiosrc/web/hapi/web/root.gohtml
@@ -0,0 +1,25 @@
+{{define "root"}}
+
+
+ {{.PageTitle}}
+
+
+
+
+
+
+
+
+
{{.PageTitle}}
+
+
+ version [todo]
+
+
+
+
+ {{.Content}}
+
+
+
+{{end}}
diff --git a/curiosrc/web/srv.go b/curiosrc/web/srv.go
index 2bc2cfa29a0..b4992dc8075 100644
--- a/curiosrc/web/srv.go
+++ b/curiosrc/web/srv.go
@@ -41,8 +41,8 @@ func GetSrv(ctx context.Context, deps *deps.Deps) (*http.Server, error) {
var static fs.FS = static
if webDev {
- basePath = "cmd/curio/web/static"
- static = os.DirFS(basePath)
+ basePath = ""
+ static = os.DirFS("curiosrc/web/static")
}
mx.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/curiosrc/web/static/main.css b/curiosrc/web/static/main.css
index e6612b0ac07..d9f31442086 100644
--- a/curiosrc/web/static/main.css
+++ b/curiosrc/web/static/main.css
@@ -35,16 +35,16 @@ table tr td:first-child, table tr th:first-child {
border-left: none;
}
-a:link {
- color: #cfc;
+a {
+ text-decoration: none;
}
-a:visited {
- color: #dfa;
+a:link, a:visited {
+ color: #adf7ad;
}
a:hover {
- color: #af7;
+ color: #88cc60;
}
.success {
diff --git a/curiosrc/web/static/pipeline_porep.html b/curiosrc/web/static/pipeline_porep.html
index ed90186de63..8696bdd26ea 100644
--- a/curiosrc/web/static/pipeline_porep.html
+++ b/curiosrc/web/static/pipeline_porep.html
@@ -75,7 +75,7 @@ Sectors
State |
-
+
diff --git a/lib/harmony/harmonytask/harmonytask.go b/lib/harmony/harmonytask/harmonytask.go
index 01f2febba62..2d8036e2fc8 100644
--- a/lib/harmony/harmonytask/harmonytask.go
+++ b/lib/harmony/harmonytask/harmonytask.go
@@ -178,7 +178,7 @@ func New(
}
}
if !h.considerWork(workSourceRecover, []TaskID{TaskID(w.ID)}) {
- log.Error("Strange: Unable to accept previously owned task: ", w.ID, w.Name)
+ log.Errorw("Strange: Unable to accept previously owned task", "id", w.ID, "type", w.Name)
}
}
}