diff --git a/server/middlewares.go b/server/middlewares.go index cf1c797..eb15fb7 100644 --- a/server/middlewares.go +++ b/server/middlewares.go @@ -17,7 +17,7 @@ func (server *Server) setupMiddlewares() { server.Router.Use(middleware.CleanPath) server.Router.Use(middleware.RedirectSlashes) server.Router.Use(middleware.AllowContentEncoding("deflate", "gzip")) - server.Router.Use(middleware.Compress(5, "text/html", "text/js", "text/css")) + server.Router.Use(middleware.Compress(5, "text/html", "text/css")) server.Router.Use(cors.Handler(cors.Options{ AllowedOrigins: server.AllowedHosts, AllowedMethods: []string{"GET", "OPTIONS"}, diff --git a/server/router.go b/server/router.go index 8d37054..d31e06b 100644 --- a/server/router.go +++ b/server/router.go @@ -9,7 +9,11 @@ import ( func (server *Server) setupRouter() { server.Router.Get("/", launchpad) server.Router.Route("/api", func(r chi.Router) { - r.Get("/ws", webSocket) + r.Route("/system", func(r chi.Router) { + r.Get("/static", routeStaticSystem) + r.Get("/live", routeLiveSystem) + r.Get("/ws", webSocket) + }) r.Get("/weather", getWeather) }) server.Router.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) { diff --git a/server/routes.go b/server/routes.go index 78fbd35..2cba29f 100644 --- a/server/routes.go +++ b/server/routes.go @@ -7,6 +7,7 @@ import ( "godash/files" "godash/hub" "godash/message" + "godash/system" "godash/weather" "net/http" ) @@ -16,6 +17,7 @@ type launchpadInformation struct { Host string Bookmarks []bookmark.Bookmark Weather weather.OpenWeatherApiResponse + System system.LiveInformation } func launchpad(w http.ResponseWriter, r *http.Request) { @@ -24,6 +26,7 @@ func launchpad(w http.ResponseWriter, r *http.Request) { Title: "Godash", Bookmarks: bookmark.Bookmarks, Weather: weather.CurrentOpenWeather, + System: system.Sys.Live, }) } @@ -43,6 +46,38 @@ func getWeather(w http.ResponseWriter, r *http.Request) { } } +// @Schemes +// @Summary live system information +// @Description gets live information of the system +// @Tags system +// @Produce json +// @Success 200 {object} system.LiveInformation +// @Success 204 {object} message.Response +// @Router /system/live [get] +func routeLiveSystem(w http.ResponseWriter, r *http.Request) { + if system.Config.LiveSystem { + jsonResponse(w, system.Sys.Live, http.StatusOK) + } else { + jsonResponse(w, message.Response{Message: message.NotFound.String()}, http.StatusNoContent) + } +} + +// @Schemes +// @Summary static system information +// @Description gets static information of the system +// @Tags system +// @Produce json +// @Success 200 {object} system.StaticInformation +// @Success 204 {object} message.Response +// @Router /system/static [get] +func routeStaticSystem(w http.ResponseWriter, r *http.Request) { + if system.Config.LiveSystem { + jsonResponse(w, system.Sys.Static, http.StatusOK) + } else { + jsonResponse(w, message.Response{Message: message.NotFound.String()}, http.StatusNoContent) + } +} + func webSocket(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { diff --git a/static/js/app.js b/static/js/app.js index c4e1516..9d5206e 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1,6 +1,6 @@ const WsType = { Weather: 0, System: 1 }; const apiBase = window.location.origin + "/api"; -let socket = new WebSocket(apiBase.replace("http", "ws") + "/ws"); +let socket = new WebSocket(apiBase.replace("http", "ws") + "/system/ws"); const weatherIcon = document.getElementById("weatherIcon"); const weatherTemp = document.getElementById("weatherTemp"); const weatherDescription = document.getElementById("weatherDescription"); diff --git a/system/cpu.go b/system/cpu.go new file mode 100644 index 0000000..9134e27 --- /dev/null +++ b/system/cpu.go @@ -0,0 +1,29 @@ +package system + +import ( + "github.com/shirou/gopsutil/v3/cpu" + "math" + "runtime" +) + +func staticCpu() CPU { + var p CPU + p.Threads = runtime.NumCPU() + p.Architecture = runtime.GOARCH + c, err := cpu.Info() + if err == nil { + p.Name = c[0].ModelName + } else { + p.Name = "none detected" + } + return p +} + +func (s *System) liveCpu() { + p, err := cpu.Percent(0, false) + if err != nil { + return + } + s.Live.CPU.Value = s.Static.CPU.Name + s.Live.CPU.Percentage = append(s.Live.CPU.Percentage[1:], math.RoundToEven(p[0])) +} diff --git a/system/disk.go b/system/disk.go new file mode 100644 index 0000000..edc1cf2 --- /dev/null +++ b/system/disk.go @@ -0,0 +1,35 @@ +package system + +import ( + "fmt" + "github.com/dariubs/percent" + "github.com/shirou/gopsutil/v3/disk" + "math" +) + +func staticDisk() Storage { + var s Storage + d, err := disk.Usage("/") + if err != nil { + return s + } + total := d.Total + if total <= 0 { + return s + } + processStorage(&s, total) + return s +} + +func (s *System) liveDisk() { + d, err := disk.Usage("/") + if err != nil { + return + } + usage := d.Used + if usage > 0 { + niceUsage := float64(usage) / s.Static.Disk.Unit + s.Live.Disk.Value = fmt.Sprintf("%.2f", niceUsage) + s.Live.Disk.Percentage = math.RoundToEven(percent.PercentOfFloat(niceUsage, s.Static.Disk.Value)) + } +} diff --git a/system/ram.go b/system/ram.go new file mode 100644 index 0000000..889cf0c --- /dev/null +++ b/system/ram.go @@ -0,0 +1,36 @@ +package system + +import ( + "fmt" + "github.com/dariubs/percent" + "github.com/shirou/gopsutil/v3/mem" + "math" +) + +func staticRam() Storage { + var s Storage + r, err := mem.VirtualMemory() + if err != nil { + return s + } + total := r.Total + if total <= 0 { + return s + } + processStorage(&s, total) + return s +} + +func (s *System) liveRam() { + r, err := mem.VirtualMemory() + if err != nil { + return + } + var niceUsage float64 = 0 + used := r.Used + if used > 0 { + niceUsage = float64(used) / s.Static.Ram.Unit + s.Live.Ram.Value = fmt.Sprintf("%.2f", niceUsage) + s.Live.Ram.Percentage = math.RoundToEven(percent.PercentOfFloat(niceUsage, s.Static.Ram.Value)) + } +} diff --git a/system/storage.go b/system/storage.go new file mode 100644 index 0000000..e3323a5 --- /dev/null +++ b/system/storage.go @@ -0,0 +1,14 @@ +package system + +import ( + "fmt" + "github.com/jaypipes/ghw/pkg/unitutil" +) + +func processStorage(s *Storage, total uint64) { + unit, unitStr := unitutil.AmountString(int64(total)) + s.Unit = float64(unit) + s.Value = float64(total) / s.Unit + s.UnitString = unitStr + s.Readable = fmt.Sprintf("%.2f %s", s.Value, s.UnitString) +} diff --git a/system/system.go b/system/system.go new file mode 100644 index 0000000..9fcf155 --- /dev/null +++ b/system/system.go @@ -0,0 +1,38 @@ +package system + +import ( + "github.com/sirupsen/logrus" + "godash/config" + "godash/hub" + "time" +) + +var Config = SystemConfig{} +var Sys = System{} + +func init() { + config.ParseViperConfig(&Config, config.AddViperConfig("system")) + if Config.LiveSystem { + Sys.Initialize() + } +} + +func (s *System) UpdateLiveInformation() { + for { + s.liveCpu() + s.liveRam() + s.liveDisk() + s.uptime() + hub.LiveInformationCh <- hub.Message{WsType: hub.System, Message: s.Live} + time.Sleep(1 * time.Second) + } +} + +func (s *System) Initialize() { + s.Static.CPU = staticCpu() + s.Static.Ram = staticRam() + s.Static.Disk = staticDisk() + s.Live.CPU.Percentage = make([]float64, 120) + go s.UpdateLiveInformation() + logrus.WithFields(logrus.Fields{"cpu": s.Static.CPU.Name, "arch": s.Static.CPU.Architecture}).Debug("system updated") +} diff --git a/system/types.go b/system/types.go new file mode 100644 index 0000000..16f4d0a --- /dev/null +++ b/system/types.go @@ -0,0 +1,46 @@ +package system + +type SystemConfig struct { + LiveSystem bool `mapstructure:"LIVE_SYSTEM"` +} + +type BasicSystemInformation struct { + Value string `json:"value" validate:"required"` + Percentage float64 `json:"percentage" validate:"required"` +} + +type LiveInformation struct { + CPU CpuSystemInformation `json:"cpu" validate:"required"` + Ram BasicSystemInformation `json:"ram" validate:"required"` + Disk BasicSystemInformation `json:"disk" validate:"required"` + ServerUptime uint64 `json:"server_uptime" validate:"required"` +} + +type StaticInformation struct { + CPU CPU `json:"cpu" validate:"required"` + Ram Storage `json:"ram" validate:"required"` + Disk Storage `json:"disk" validate:"required"` +} + +type System struct { + Live LiveInformation `json:"live" validate:"required"` + Static StaticInformation `json:"static" validate:"required"` +} + +type Storage struct { + Readable string `json:"readable" validate:"required"` + Value float64 `json:"value" validate:"required"` + UnitString string `json:"unit_string" validate:"required"` + Unit float64 `json:"unit" validate:"required"` +} + +type CPU struct { + Name string `json:"name" validate:"required"` + Threads int `json:"threads" validate:"required"` + Architecture string `json:"architecture" validate:"required"` +} + +type CpuSystemInformation struct { + Value string `json:"value" validate:"required"` + Percentage []float64 `json:"percentage" validate:"required"` +} diff --git a/system/uptime.go b/system/uptime.go new file mode 100644 index 0000000..1210399 --- /dev/null +++ b/system/uptime.go @@ -0,0 +1,14 @@ +package system + +import ( + "github.com/shirou/gopsutil/v3/host" +) + +func (s *System) uptime() { + i, err := host.Info() + if err != nil { + return + } + // returns uptime in milliseconds + s.Live.ServerUptime = i.Uptime * 1000 +}