debugWeb 1

This commit is contained in:
Andrew Jackson (Ajax) 2023-12-11 23:16:57 -06:00
parent a478b734fd
commit 4161c270d7
8 changed files with 423 additions and 3 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/gorilla/mux"
logging "github.com/ipfs/go-log/v2"
"go.opencensus.io/tag"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-jsonrpc"
@ -20,6 +21,7 @@ import (
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
"github.com/filecoin-project/lotus/cmd/lotus-provider/web"
"github.com/filecoin-project/lotus/lib/rpcenc"
"github.com/filecoin-project/lotus/metrics"
"github.com/filecoin-project/lotus/metrics/proxy"
@ -126,15 +128,27 @@ func ListenAndServe(ctx context.Context, dependencies *deps.Deps, shutdownChan c
Addr: dependencies.ListenAddr,
}
log.Infof("Setting up RPC server at %s", dependencies.ListenAddr)
web, err := web.GetSrv(ctx, dependencies)
if err != nil {
return err
}
go func() {
<-ctx.Done()
log.Warn("Shutting down...")
if err := srv.Shutdown(context.TODO()); err != nil {
log.Errorf("shutting down RPC server failed: %s", err)
}
if err := web.Shutdown(context.Background()); err != nil {
log.Errorf("shutting down web server failed: %s", err)
}
log.Warn("Graceful shutdown successful")
}()
log.Infof("Setting up RPC server at %s", dependencies.ListenAddr)
return srv.ListenAndServe()
eg := errgroup.Group{}
eg.Go(srv.ListenAndServe)
eg.Go(web.ListenAndServe)
return eg.Wait()
}

View File

@ -0,0 +1,95 @@
// Package debug provides the API for various debug endpoints in lotus-provider.
package debug
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
logging "github.com/ipfs/go-log/v2"
"github.com/filecoin-project/lotus/build"
cliutil "github.com/filecoin-project/lotus/cli/util"
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
)
var log = logging.Logger("lp/web/debug")
type debug struct {
*deps.Deps
}
func Routes(r *mux.Router, deps *deps.Deps) {
d := debug{deps}
r.Methods("GET").Path("chain-state-sse").HandlerFunc(d.chainStateSSE)
}
type rpcInfo struct {
Address string
CLayers []string
Reachable bool
SyncState string
Version string
}
func (d *debug) chainStateSSE(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
v1api := d.Deps.Full
ctx := r.Context()
ai := cliutil.ParseApiInfo(d.Deps.Cfg.Apis.ChainApiInfo[0])
ver, err := v1api.Version(ctx)
if err != nil {
log.Warnw("Version", "error", err)
return
}
sse:
for {
head, err := v1api.ChainHead(ctx)
if err != nil {
log.Warnw("ChainHead", "error", err)
return
}
var syncState string
switch {
case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*3/2): // within 1.5 epochs
syncState = "ok"
case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*5): // within 5 epochs
syncState = fmt.Sprintf("slow (%s behind)", time.Since(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second))
default:
syncState = fmt.Sprintf("behind (%s behind)", time.Since(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second))
}
select {
case <-ctx.Done():
break sse
default:
}
fmt.Fprintf(w, "data: ")
err = json.NewEncoder(w).Encode(rpcInfo{
Address: ai.Addr,
CLayers: []string{},
Reachable: true,
Version: ver.Version,
SyncState: syncState,
})
if err != nil {
log.Warnw("json encode", "error", err)
return
}
fmt.Fprintf(w, "\n\n")
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
}

View File

@ -0,0 +1,12 @@
// Package api provides the HTTP API for the lotus provider web gui.
package api
import (
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
"github.com/filecoin-project/lotus/cmd/lotus-provider/web/api/debug"
"github.com/gorilla/mux"
)
func Routes(r *mux.Router, deps *deps.Deps) {
debug.Routes(r.PathPrefix("/debug").Subrouter(), deps)
}

View File

@ -0,0 +1,40 @@
// Package web defines the HTTP web server for static files and endpoints.
package web
import (
"context"
"embed"
"net"
"net/http"
"strings"
"github.com/filecoin-project/lotus/cmd/lotus-provider/deps"
"github.com/filecoin-project/lotus/cmd/lotus-provider/web/api"
"github.com/filecoin-project/lotus/metrics"
"github.com/gorilla/mux"
"go.opencensus.io/tag"
)
// go:embed static
var static embed.FS
func GetSrv(ctx context.Context, deps *deps.Deps) (*http.Server, error) {
mux := mux.NewRouter()
api.Routes(mux.PathPrefix("/api").Subrouter(), deps)
mux.NotFoundHandler = http.FileServer(http.FS(static))
return &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/") {
r.URL.Path = r.URL.Path + "index.html"
return
}
mux.ServeHTTP(w, r)
}),
BaseContext: func(listener net.Listener) context.Context {
ctx, _ := tag.New(context.Background(), tag.Upsert(metrics.APIInterface, "lotus-provider"))
return ctx
},
Addr: deps.Cfg.Subsystems.GuiAddress,
}, nil
}

View File

@ -0,0 +1,181 @@
<html>
<head>
<title>Lotus Provider Cluster Overview</title>
<script module src="chain-connectivity.js"></script>
<style>
html,
body {
background: #0f0f0f;
color: #ffffff;
padding: 0;
margin: 0;
font-family: monospace;
}
.app-head {
width: 100%;
}
.head-left {
display: inline-block;
}
.head-right {
display: inline-block;
float: right;
}
a:link {
color: #cfc;
}
a:visited {
color: #dfa;
}
a:hover {
color: #af7;
}
.dash-tile {
display: flex;
flex-direction: column;
padding: 0.75rem;
background: #3f3f3f;
& b {
padding-bottom: 0.5rem;
color: deeppink;
}
}
.deadline-box {
display: grid;
grid-template-columns: repeat(16, auto);
grid-template-rows: repeat(3, auto);
grid-gap: 1px;
}
.deadline-entry {
width: 10px;
height: 10px;
background-color: grey;
margin: 1px;
}
.deadline-entry-cur {
border-bottom: 3px solid deepskyblue;
height: 7px;
}
.deadline-proven {
background-color: green;
}
.deadline-partially-faulty {
background-color: yellow;
}
.deadline-faulty {
background-color: red;
}
</style>
</head>
<body>
<div class="app-head">
<div class="head-left">
<h1>Lotus Provider Cluster</h1>
</div>
<div class="head-right">
version [todo]
</div>
</div>
<hr />
<div class="page">
<div class="info-block">
<h2>Chain Connectivity</h2>
<chain-connectivity></chain-connectivity>
</div>
</div>
<hr>
<div class="info-block">
<h2>Actor Summary</h2>
<table>
<thead>
<tr>
<th>Address</th>
<th>Config Layers</th>
<th>QaP</th>
<th>Deadlines</th>
</tr>
</thead>
<tbody>
<tr>
<td>f01234</td>
<td>mig0</td>
<td>23TiB</td>
<td>
<div class="deadline-box">
<div class="deadline-entry deadline-proven"></div>
<div class="deadline-entry deadline-partially-faulty"></div>
<div class="deadline-entry deadline-faulty"></div>
<div class="deadline-entry deadline-proven"></div>
<div class="deadline-entry deadline-proven"></div>
<div class="deadline-entry deadline-entry-cur"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
<div class="deadline-entry"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,73 @@
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/npm/lit-html@3.1.0/lit-html.min.js';
window.customElements.define('chain-connectivity', class MyElement extends LitElement {
constructor() {
super();
this.data = [];
this.loadData();
}
loadData() {
const eventSource = new EventSource('/api/debug/chain-state-sse');
eventSource.onmessage = (event) => {
this.data.push(JSON.parse(event.data));
};
eventSource.onerror = (error) => {
console.error('Error:', error);
loadData();
};
};
static get styles() {
return [css`
:host {
box-sizing: border-box; /* Don't forgert this to include padding/border inside width calculation */
}
table {
border-collapse: collapse;
}
table td, table th {
border-left: 1px solid #f0f0f0;
padding: 1px 5px;
}
table tr td:first-child, table tr th:first-child {
border-left: none;
}
.success {
color: green;
}
.warning {
color: yellow;
}
.error {
color: red;
}
`];
}
render = () => html`
<table>
<thead>
<tr>
<th>RPC Address</th>
<th>Reachability</th>
<th>Sync Status</th>
<th>Version</th>
</tr>
</thead>
<tbody>
${this.data.map(item => html`
<tr>
<td>{{.Address}}</td>
<td>${item.Address}</td>
<td>${item.Reachable ? html`<span class="success">ok</span>` : html`<span class="error">FAIL</span>`}</td>
<td>${item.SyncState === "ok" ? html`<span class="success">ok</span>` : html`<span class="warning">${item.SyncState}</span>`}</td>
<td>${item.Version}</td>
</tr>
`)}
<tr>
<td colspan="4">Data incoming...</td>
</tr>
</tbody>
</table>`
});

View File

@ -351,7 +351,9 @@ func DefaultUserRaftConfig() *UserRaftConfig {
func DefaultLotusProvider() *LotusProviderConfig {
return &LotusProviderConfig{
Subsystems: ProviderSubsystemsConfig{},
Subsystems: ProviderSubsystemsConfig{
GuiAddress: ":4701",
},
Fees: LotusProviderFees{
DefaultMaxFee: DefaultDefaultMaxFee,
MaxPreCommitGasFee: types.MustParseFIL("0.025"),

View File

@ -96,6 +96,9 @@ type ProviderSubsystemsConfig struct {
WindowPostMaxTasks int
EnableWinningPost bool
WinningPostMaxTasks int
// The address that should listen for Web GUI requests.
GuiAddress string
}
type DAGStoreConfig struct {