From 094444159759e1e0c5ce6ab50ca8efabf76b5e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Wilczy=C5=84ski?= Date: Wed, 6 Jan 2021 19:50:02 +0900 Subject: [PATCH] Revert "gui, lib/api: Remove CPU & RAM measurements (fixes #6249) (#6393)" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c7d6a6d78042876d9a27b13df2ae45783dca7c37. Signed-off-by: Tomasz WilczyƄski --- gui/default/index.html | 8 ++++ lib/api/api.go | 14 ++++-- lib/api/api_test.go | 7 +-- lib/api/mocked_cpuusage_test.go | 13 ++++++ lib/rc/rc.go | 1 + lib/syncthing/cpuusage.go | 60 ++++++++++++++++++++++++ lib/syncthing/cpuusage_solaris.go | 78 +++++++++++++++++++++++++++++++ lib/syncthing/cpuusage_unix.go | 18 +++++++ lib/syncthing/cpuusage_windows.go | 27 +++++++++++ lib/syncthing/syncthing.go | 5 +- 10 files changed, 224 insertions(+), 7 deletions(-) create mode 100644 lib/api/mocked_cpuusage_test.go create mode 100644 lib/syncthing/cpuusage.go create mode 100644 lib/syncthing/cpuusage_solaris.go create mode 100644 lib/syncthing/cpuusage_unix.go create mode 100644 lib/syncthing/cpuusage_windows.go diff --git a/gui/default/index.html b/gui/default/index.html index fc009128ec4..2c2bb0d0f28 100644 --- a/gui/default/index.html +++ b/gui/default/index.html @@ -652,6 +652,14 @@

+ +  RAM Utilization + {{system.sys | binary}}B + + +  CPU Utilization + {{system.cpuPercent | alwaysNumber | percent}} +  Listeners diff --git a/lib/api/api.go b/lib/api/api.go index 79b1d395c9f..d854dabc250 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -81,6 +81,7 @@ type service struct { connectionsService connections.Service fss model.FolderSummaryService urService *ur.Service + cpu Rater noUpgrade bool tlsDefaultCommonName string configChanged chan struct{} // signals intentional listener close due to config change @@ -94,13 +95,17 @@ type service struct { systemLog logger.Recorder } +type Rater interface { + Rate() float64 +} + type Service interface { suture.Service config.Committer WaitForStart() error } -func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, evLogger events.Logger, discoverer discover.Manager, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, noUpgrade bool) Service { +func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, evLogger events.Logger, discoverer discover.Manager, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, cpu Rater, noUpgrade bool) Service { return &service{ id: id, cfg: cfg, @@ -118,6 +123,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam urService: urService, guiErrors: errors, systemLog: systemLog, + cpu: cpu, noUpgrade: noUpgrade, tlsDefaultCommonName: tlsDefaultCommonName, configChanged: make(chan struct{}), @@ -313,7 +319,7 @@ func (s *service) Serve(ctx context.Context) error { configBuilder.registerGUI("/rest/config/gui") // Deprecated config endpoints - configBuilder.registerConfigDeprecated("/rest/system/config") // POST instead of PUT + configBuilder.registerConfig("/rest/system/config") // POST instead of PUT configBuilder.registerConfigInsync("/rest/system/config/insync") // Debug endpoints, not for general use @@ -1026,7 +1032,9 @@ func (s *service) getSystemStatus(w http.ResponseWriter, r *http.Request) { res["connectionServiceStatus"] = s.connectionsService.ListenerStatus() res["lastDialStatus"] = s.connectionsService.ConnectionStatus() - res["cpuPercent"] = 0 // deprecated from API + // cpuUsage.Rate() is in milliseconds per second, so dividing by ten + // gives us percent + res["cpuPercent"] = s.cpu.Rate() / 10 / float64(runtime.NumCPU()) res["pathSeparator"] = string(filepath.Separator) res["urVersionMax"] = ur.Version res["uptime"] = s.urService.UptimeS() diff --git a/lib/api/api_test.go b/lib/api/api_test.go index c20296a7d75..6032079d0f7 100644 --- a/lib/api/api_test.go +++ b/lib/api/api_test.go @@ -124,7 +124,7 @@ func TestStopAfterBrokenConfig(t *testing.T) { } w := config.Wrap("/dev/null", cfg, protocol.LocalDeviceID, events.NoopLogger) - srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, false).(*service) + srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, nil, false).(*service) defer os.Remove(token) srv.started = make(chan string) @@ -605,13 +605,14 @@ func startHTTP(cfg config.Wrapper) (string, context.CancelFunc, error) { }, }) } + cpu := new(mockedCPUService) addrChan := make(chan string) mockedSummary := &modelmocks.FolderSummaryService{} mockedSummary.SummaryReturns(map[string]interface{}{"mocked": true}, nil) // Instantiate the API service urService := ur.New(cfg, m, connections, false) - svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, events.NoopLogger, discoverer, connections, urService, mockedSummary, errorLog, systemLog, false).(*service) + svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, events.NoopLogger, discoverer, connections, urService, mockedSummary, errorLog, systemLog, cpu, false).(*service) defer os.Remove(token) svc.started = addrChan @@ -1102,7 +1103,7 @@ func TestEventMasks(t *testing.T) { cfg := newMockedConfig() defSub := new(eventmocks.BufferedSubscription) diskSub := new(eventmocks.BufferedSubscription) - svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, events.NoopLogger, nil, nil, nil, nil, nil, nil, false).(*service) + svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, events.NoopLogger, nil, nil, nil, nil, nil, nil, nil, false).(*service) defer os.Remove(token) if mask := svc.getEventMask(""); mask != DefaultEventMask { diff --git a/lib/api/mocked_cpuusage_test.go b/lib/api/mocked_cpuusage_test.go new file mode 100644 index 00000000000..c89a1dbadc6 --- /dev/null +++ b/lib/api/mocked_cpuusage_test.go @@ -0,0 +1,13 @@ +// Copyright (C) 2017 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package api + +type mockedCPUService struct{} + +func (*mockedCPUService) Rate() float64 { + return 42 +} diff --git a/lib/rc/rc.go b/lib/rc/rc.go index f21d0e31df7..f953ac851fb 100644 --- a/lib/rc/rc.go +++ b/lib/rc/rc.go @@ -618,6 +618,7 @@ func (p *Process) Connections() (map[string]ConnectionStats, error) { type SystemStatus struct { Alloc int64 + CPUPercent float64 Goroutines int MyID protocol.DeviceID PathSeparator string diff --git a/lib/syncthing/cpuusage.go b/lib/syncthing/cpuusage.go new file mode 100644 index 00000000000..a49ad3f0b81 --- /dev/null +++ b/lib/syncthing/cpuusage.go @@ -0,0 +1,60 @@ +// Copyright (C) 2017 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package syncthing + +import ( + "context" + "math" + "time" + + metrics "github.com/rcrowley/go-metrics" +) + +const cpuTickRate = 5 * time.Second + +type cpuService struct { + avg metrics.EWMA + stop chan struct{} +} + +func newCPUService() *cpuService { + return &cpuService{ + // 10 second average. Magic alpha value comes from looking at EWMA package + // definitions of EWMA1, EWMA5. The tick rate *must* be five seconds (hard + // coded in the EWMA package). + avg: metrics.NewEWMA(1 - math.Exp(-float64(cpuTickRate)/float64(time.Second)/10.0)), + stop: make(chan struct{}), + } +} + +func (s *cpuService) Serve(ctx context.Context) error { + // Initialize prevUsage to an actual value returned by cpuUsage + // instead of zero, because at least Windows returns a huge negative + // number here that then slowly increments... + prevUsage := cpuUsage() + ticker := time.NewTicker(cpuTickRate) + defer ticker.Stop() + for { + select { + case <-ticker.C: + curUsage := cpuUsage() + s.avg.Update(int64((curUsage - prevUsage) / time.Millisecond)) + prevUsage = curUsage + s.avg.Tick() + case <-ctx.Done(): + return nil + } + } +} + +func (s *cpuService) Stop() { + close(s.stop) +} + +func (s *cpuService) Rate() float64 { + return s.avg.Rate() +} diff --git a/lib/syncthing/cpuusage_solaris.go b/lib/syncthing/cpuusage_solaris.go new file mode 100644 index 00000000000..74a4e28084e --- /dev/null +++ b/lib/syncthing/cpuusage_solaris.go @@ -0,0 +1,78 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +//+build solaris + +package syncthing + +import ( + "encoding/binary" + "fmt" + "os" + "time" +) + +type id_t int32 +type ulong_t uint32 + +type timestruc_t struct { + Tv_sec int64 + Tv_nsec int64 +} + +func (tv timestruc_t) Nano() int64 { + return tv.Tv_sec*1e9 + tv.Tv_nsec +} + +type prusage_t struct { + Pr_lwpid id_t /* lwp id. 0: process or defunct */ + Pr_count int32 /* number of contributing lwps */ + Pr_tstamp timestruc_t /* real time stamp, time of read() */ + Pr_create timestruc_t /* process/lwp creation time stamp */ + Pr_term timestruc_t /* process/lwp termination time stamp */ + Pr_rtime timestruc_t /* total lwp real (elapsed) time */ + Pr_utime timestruc_t /* user level CPU time */ + Pr_stime timestruc_t /* system call CPU time */ + Pr_ttime timestruc_t /* other system trap CPU time */ + Pr_tftime timestruc_t /* text page fault sleep time */ + Pr_dftime timestruc_t /* data page fault sleep time */ + Pr_kftime timestruc_t /* kernel page fault sleep time */ + Pr_ltime timestruc_t /* user lock wait sleep time */ + Pr_slptime timestruc_t /* all other sleep time */ + Pr_wtime timestruc_t /* wait-cpu (latency) time */ + Pr_stoptime timestruc_t /* stopped time */ + Pr_minf ulong_t /* minor page faults */ + Pr_majf ulong_t /* major page faults */ + Pr_nswap ulong_t /* swaps */ + Pr_inblk ulong_t /* input blocks */ + Pr_oublk ulong_t /* output blocks */ + Pr_msnd ulong_t /* messages sent */ + Pr_mrcv ulong_t /* messages received */ + Pr_sigs ulong_t /* signals received */ + Pr_vctx ulong_t /* voluntary context switches */ + Pr_ictx ulong_t /* involuntary context switches */ + Pr_sysc ulong_t /* system calls */ + Pr_ioch ulong_t /* chars read and written */ + +} + +var procFile = fmt.Sprintf("/proc/%d/usage", os.Getpid()) + +func cpuUsage() time.Duration { + fd, err := os.Open(procFile) + if err != nil { + return 0 + } + + var rusage prusage_t + err = binary.Read(fd, binary.LittleEndian, rusage) + fd.Close() + if err != nil { + return 0 + } + + return time.Duration(rusage.Pr_utime.Nano() + rusage.Pr_stime.Nano()) +} diff --git a/lib/syncthing/cpuusage_unix.go b/lib/syncthing/cpuusage_unix.go new file mode 100644 index 00000000000..2b149abb2df --- /dev/null +++ b/lib/syncthing/cpuusage_unix.go @@ -0,0 +1,18 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +//+build !windows,!solaris + +package syncthing + +import "syscall" +import "time" + +func cpuUsage() time.Duration { + var rusage syscall.Rusage + syscall.Getrusage(syscall.RUSAGE_SELF, &rusage) + return time.Duration(rusage.Utime.Nano() + rusage.Stime.Nano()) +} diff --git a/lib/syncthing/cpuusage_windows.go b/lib/syncthing/cpuusage_windows.go new file mode 100644 index 00000000000..6627b8af753 --- /dev/null +++ b/lib/syncthing/cpuusage_windows.go @@ -0,0 +1,27 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +//+build windows + +package syncthing + +import "syscall" +import "time" + +func cpuUsage() time.Duration { + handle, err := syscall.GetCurrentProcess() + if err != nil { + return 0 + } + defer syscall.CloseHandle(handle) + + var ctime, etime, ktime, utime syscall.Filetime + if err := syscall.GetProcessTimes(handle, &ctime, &etime, &ktime, &utime); err != nil { + return 0 + } + + return time.Duration(ktime.Nanoseconds() + utime.Nanoseconds()) +} diff --git a/lib/syncthing/syncthing.go b/lib/syncthing/syncthing.go index 7e2a96df28c..87be1ca6d46 100644 --- a/lib/syncthing/syncthing.go +++ b/lib/syncthing/syncthing.go @@ -411,10 +411,13 @@ func (a *App) setupGUI(m model.Model, defaultSub, diskSub events.BufferedSubscri l.Warnln("Insecure admin access is enabled.") } + cpu := newCPUService() + a.mainService.Add(cpu) + summaryService := model.NewFolderSummaryService(a.cfg, m, a.myID, a.evLogger) a.mainService.Add(summaryService) - apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, a.opts.NoUpgrade) + apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, cpu, a.opts.NoUpgrade) a.mainService.Add(apiSvc) if err := apiSvc.WaitForStart(); err != nil {