This commit is contained in:
2024-02-24 11:40:26 +01:00
parent 55ce327ff5
commit 74432b1929
6 changed files with 144 additions and 35 deletions

View File

@@ -1,10 +1,12 @@
package api package api
import ( import (
"encoding/json"
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"nvidiadashboard/pkg/constant"
"nvidiadashboard/pkg/nvidia" "nvidiadashboard/pkg/nvidia"
"os" "os"
"path/filepath" "path/filepath"
@@ -37,6 +39,7 @@ type (
Username string Username string
DriverVersion string DriverVersion string
CUDAVersion string CUDAVersion string
Version string
} }
) )
@@ -72,6 +75,7 @@ func New(repo NVIDIARepository) *Server {
s.mux.Use(middleware.URLFormat) s.mux.Use(middleware.URLFormat)
s.mux.Get("/", s.handleRoot) s.mux.Get("/", s.handleRoot)
s.mux.Get("/{uuid:GPU-(.*)}/json", s.handleGPUJSON)
s.mux.Get("/{uuid:GPU-(.*)}", s.handleGPU) s.mux.Get("/{uuid:GPU-(.*)}", s.handleGPU)
s.mux.Get("/{path:(.*).html}", func(w http.ResponseWriter, r *http.Request) { s.mux.Get("/{path:(.*).html}", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@@ -95,7 +99,7 @@ func (s *Server) Serve(port uint) error {
func (s *Server) handleRoot(w http.ResponseWriter, _ *http.Request) { func (s *Server) handleRoot(w http.ResponseWriter, _ *http.Request) {
gpus, err := s.repo.GetGPUs() gpus, err := s.repo.GetGPUs()
if err != nil { if err != nil {
sendInternalServerErrorHTML(w, err) internalServerErrorHTML(w, err)
return return
} }
if len(gpus) > 0 { if len(gpus) > 0 {
@@ -105,11 +109,13 @@ func (s *Server) handleRoot(w http.ResponseWriter, _ *http.Request) {
} }
wp := WebPack{ wp := WebPack{
Username: "anonymous", Username: "anonymous",
Version: constant.Version,
} }
t, err := template.ParseFiles("static/no_gpu.html") t, err := template.ParseFiles("static/no_gpu.html")
if err != nil { if err != nil {
sendInternalServerErrorHTML(w, err) internalServerErrorHTML(w, err)
return
} }
err = t.Execute(w, wp) err = t.Execute(w, wp)
if err != nil { if err != nil {
@@ -122,7 +128,7 @@ func (s *Server) handleGPU(w http.ResponseWriter, r *http.Request) {
gpus, err := s.repo.GetGPUs() gpus, err := s.repo.GetGPUs()
if err != nil { if err != nil {
sendInternalServerErrorHTML(w, err) internalServerErrorHTML(w, err)
return return
} }
i := -1 i := -1
@@ -137,7 +143,7 @@ func (s *Server) handleGPU(w http.ResponseWriter, r *http.Request) {
} }
gpu, err := s.repo.GetGPU(i) gpu, err := s.repo.GetGPU(i)
if err != nil { if err != nil {
sendInternalServerErrorHTML(w, err) internalServerErrorHTML(w, err)
return return
} }
wp := WebPack{ wp := WebPack{
@@ -146,11 +152,13 @@ func (s *Server) handleGPU(w http.ResponseWriter, r *http.Request) {
GPU: gpu, GPU: gpu,
DriverVersion: s.repo.DriverVersion(), DriverVersion: s.repo.DriverVersion(),
CUDAVersion: s.repo.CUDAVersion(), CUDAVersion: s.repo.CUDAVersion(),
Version: constant.Version,
} }
t, err := template.New("index").Funcs(templateFuncMap).ParseFiles("static/index.html") t, err := template.New("index").Funcs(templateFuncMap).ParseFiles("static/index.html")
if err != nil { if err != nil {
sendInternalServerErrorHTML(w, err) internalServerErrorHTML(w, err)
return
} }
err = t.ExecuteTemplate(w, "index.html", wp) err = t.ExecuteTemplate(w, "index.html", wp)
if err != nil { if err != nil {
@@ -158,6 +166,38 @@ func (s *Server) handleGPU(w http.ResponseWriter, r *http.Request) {
} }
} }
func (s *Server) handleGPUJSON(w http.ResponseWriter, r *http.Request) {
uuid := chi.URLParam(r, "uuid")
gpus, err := s.repo.GetGPUs()
if err != nil {
internalServerErrorJSON(w, err)
return
}
i := -1
for _, gpu := range gpus {
if gpu.UUID == uuid {
i = gpu.Index
}
}
if i == -1 {
notFoundJSON(w)
return
}
gpu, err := s.repo.GetGPU(i)
if err != nil {
internalServerErrorJSON(w, err)
return
}
body, err := json.Marshal(gpu)
if err != nil {
internalServerErrorJSON(w, err)
return
}
w.Header().Add("content-type", "application/json")
w.Write([]byte(body))
}
// FileServer conveniently sets up a http.FileServer handler to serve // FileServer conveniently sets up a http.FileServer handler to serve
// static files from a http.FileSystem. // static files from a http.FileSystem.
func fileServer(r chi.Router, path string, root http.FileSystem) { func fileServer(r chi.Router, path string, root http.FileSystem) {
@@ -179,7 +219,7 @@ func fileServer(r chi.Router, path string, root http.FileSystem) {
}) })
} }
func sendInternalServerErrorHTML(w http.ResponseWriter, v any) { func internalServerErrorHTML(w http.ResponseWriter, v any) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
body := fmt.Sprintf(internalServerErrorPage, v) body := fmt.Sprintf(internalServerErrorPage, v)
w.Write([]byte(body)) w.Write([]byte(body))
@@ -191,6 +231,6 @@ func convertByteSize(v int) string {
} }
func percentageRounded(a, b int) int { func percentageRounded(a, b int) int {
p := (a / b) * 100 p := (float64(a) / float64(b)) * 100
return p return int(p)
} }

58
api/json.go Normal file
View File

@@ -0,0 +1,58 @@
package api
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
)
type (
JSONBase struct {
Status int `json:"status"`
Timestamp time.Time `json:"timestamp"`
}
JSONError struct {
JSONBase
Message string
}
)
var (
errLog = log.New(os.Stderr, log.Prefix(), log.Flags())
)
func internalServerErrorJSON(w http.ResponseWriter, v any) {
w.WriteHeader(http.StatusInternalServerError)
body, err := json.Marshal(JSONError{
JSONBase: JSONBase{
Status: http.StatusInternalServerError,
Timestamp: time.Now(),
},
Message: fmt.Sprintf("%v", v),
})
if err != nil {
errLog.Println("[ERROR]", err)
}
w.Write([]byte(body))
}
func notFoundJSON(w http.ResponseWriter) {
w.WriteHeader(http.StatusNotFound)
body, err := json.Marshal(JSONError{
JSONBase: JSONBase{
Status: http.StatusNotFound,
Timestamp: time.Now(),
},
Message: "404 page not found",
})
if err != nil {
errLog.Println("[ERROR]", err)
}
w.Write([]byte(body))
}

View File

@@ -1,12 +1,16 @@
package main package main
import ( import (
"fmt"
"log" "log"
"nvidiadashboard/api" "nvidiadashboard/api"
"nvidiadashboard/pkg/constant"
"nvidiadashboard/pkg/nvidia" "nvidiadashboard/pkg/nvidia"
) )
func main() { func main() {
fmt.Println("*** NVIDIA Web Dashboard -", constant.Version, "***")
r := nvidia.New() r := nvidia.New()
defer r.Close() defer r.Close()

5
pkg/constant/constant.go Normal file
View File

@@ -0,0 +1,5 @@
package constant
const (
Version = "0.1-alpha"
)

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/NVIDIA/go-nvml/pkg/nvml" "github.com/NVIDIA/go-nvml/pkg/nvml"
) )
@@ -16,43 +17,43 @@ type (
} }
GPU struct { GPU struct {
Name string Name string `json:"name"`
UUID string UUID string `json:"uuid"`
Index int Index int `json:"-"`
} }
GPUDetail struct { GPUDetail struct {
GPU GPU
CoreTemperature int CoreTemperature int `json:"coreTemperature"`
Utilization Utilization Utilization Utilization `json:"usage"`
Processes []Process Processes []Process `json:"processes"`
Memory Memory Memory Memory `json:"memory"`
Fans []Fan Fans []Fan `json:"fans"`
} }
Memory struct { Memory struct {
Free int Free int `json:"free"`
Reserved int Reserved int `json:"reserved"`
Total int Total int `json:"total"`
Used int Used int `json:"used"`
Version int Version int `json:"-"`
} }
Process struct { Process struct {
PID int PID int `json:"pid"`
MemoryUsed int MemoryUsed int `json:"memoryUsed"`
Name string Name string `json:"commandLine"`
Type string Type string `json:"type"`
} }
Utilization struct { Utilization struct {
Decode int Decoder int `json:"decoder"`
Encode int Encoder int `json:"encoder"`
Rate int Compute int `json:"compute"`
} }
Fan struct { Fan struct {
Speed int Speed int `json:"speed"`
} }
) )
@@ -196,9 +197,10 @@ func (*Repository) GetGPU(ID int) (GPUDetail, error) {
} }
// Find process command line // Find process command line
for _, process := range allProcess { for i, process := range allProcess {
if cmdline, err := os.ReadFile("/proc/" + strconv.Itoa(process.PID) + "/cmdline"); err == nil { if cmdline, err := os.ReadFile("/proc/" + strconv.Itoa(process.PID) + "/cmdline"); err == nil {
process.Name = string(cmdline) process.Name = strings.ReplaceAll(string(cmdline), "\u0000", " ")
allProcess[i] = process
} }
} }
@@ -211,9 +213,9 @@ func (*Repository) GetGPU(ID int) (GPUDetail, error) {
GPU: gpu, GPU: gpu,
CoreTemperature: int(temp), CoreTemperature: int(temp),
Utilization: Utilization{ Utilization: Utilization{
Decode: int(decUsage), Decoder: int(decUsage),
Encode: int(encUsage), Encoder: int(encUsage),
Rate: int(load.Gpu), Compute: int(load.Gpu),
}, },
Processes: allProcess, Processes: allProcess,
Memory: Memory{ Memory: Memory{

2
static

Submodule static updated: de2d3dc85f...369059c925