Browse Source

Report CPU/mem usage in GUI

Jakob Borg 12 years ago
parent
commit
832c0ffad0
5 changed files with 140 additions and 53 deletions
  1. 23 0
      gui.go
  2. 20 14
      gui/app.js
  3. 56 39
      gui/index.html
  4. 31 0
      gui_unix.go
  5. 10 0
      main.go

+ 23 - 0
gui.go

@@ -8,6 +8,8 @@ import (
 	"mime"
 	"net/http"
 	"path/filepath"
+	"runtime"
+	"sync"
 
 	"bitbucket.org/tebeka/nrsc"
 	"github.com/calmh/syncthing/model"
@@ -22,6 +24,7 @@ func startGUI(addr string, m *model.Model) {
 	router.Get("/rest/connections", restGetConnections)
 	router.Get("/rest/config", restGetConfig)
 	router.Get("/rest/need", restGetNeed)
+	router.Get("/rest/system", restGetSystem)
 
 	go func() {
 		mr := martini.New()
@@ -34,6 +37,7 @@ func startGUI(addr string, m *model.Model) {
 			warnln("GUI not possible:", err)
 		}
 	}()
+
 }
 
 func getRoot(w http.ResponseWriter, r *http.Request) {
@@ -100,6 +104,25 @@ func restGetNeed(m *model.Model, w http.ResponseWriter) {
 	json.NewEncoder(w).Encode(gfs)
 }
 
+var cpuUsagePercent float64
+var cpuUsageLock sync.RWMutex
+
+func restGetSystem(w http.ResponseWriter) {
+	var m runtime.MemStats
+	runtime.ReadMemStats(&m)
+
+	res := make(map[string]interface{})
+	res["goroutines"] = runtime.NumGoroutine()
+	res["alloc"] = m.Alloc
+	res["sys"] = m.Sys
+	cpuUsageLock.RLock()
+	res["cpuPercent"] = cpuUsagePercent
+	cpuUsageLock.RUnlock()
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(res)
+}
+
 func nrscStatic(path string) interface{} {
 	if err := nrsc.Initialize(); err != nil {
 		panic("Unable to initialize nrsc: " + err.Error())

+ 20 - 14
gui/app.js

@@ -26,6 +26,9 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
     });
 
     $scope.refresh = function () {
+        $http.get("/rest/system").success(function (data) {
+            $scope.system = data;
+        });
         $http.get("/rest/model").success(function (data) {
             $scope.model = data;
             modelGetSucceeded();
@@ -71,16 +74,19 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
     setInterval($scope.refresh, 10000);
 });
 
-function decimals(num) {
-    if (num > 100) {
-        return 0;
-    }
-    if (num > 10) {
-        return 1;
-    }
-    return 2;
+function decimals(val, num) {
+    if (val === 0) { return 0; }
+    var digits = Math.floor(Math.log(Math.abs(val))/Math.log(10));
+    var decimals = Math.max(0, num - digits);
+    return decimals;
 }
 
+syncthing.filter('natural', function() {
+    return function(input, valid) {
+        return input.toFixed(decimals(input, valid));
+    }
+});
+
 syncthing.filter('binary', function() {
     return function(input) {
         if (input === undefined) {
@@ -88,15 +94,15 @@ syncthing.filter('binary', function() {
         }
         if (input > 1024 * 1024 * 1024) {
             input /= 1024 * 1024 * 1024;
-            return input.toFixed(decimals(input)) + ' Gi';
+            return input.toFixed(decimals(input, 2)) + ' Gi';
         }
         if (input > 1024 * 1024) {
             input /= 1024 * 1024;
-            return input.toFixed(decimals(input)) + ' Mi';
+            return input.toFixed(decimals(input, 2)) + ' Mi';
         }
         if (input > 1024) {
             input /= 1024;
-            return input.toFixed(decimals(input)) + ' Ki';
+            return input.toFixed(decimals(input, 2)) + ' Ki';
         }
         return Math.round(input) + ' ';
     }
@@ -109,15 +115,15 @@ syncthing.filter('metric', function() {
         }
         if (input > 1000 * 1000 * 1000) {
             input /= 1000 * 1000 * 1000;
-            return input.toFixed(decimals(input)) + ' G';
+            return input.toFixed(decimals(input, 2)) + ' G';
         }
         if (input > 1000 * 1000) {
             input /= 1000 * 1000;
-            return input.toFixed(decimals(input)) + ' M';
+            return input.toFixed(decimals(input, 2)) + ' M';
         }
         if (input > 1000) {
             input /= 1000;
-            return input.toFixed(decimals(input)) + ' k';
+            return input.toFixed(decimals(input, 2)) + ' k';
         }
         return Math.round(input) + ' ';
     }

+ 56 - 39
gui/index.html

@@ -47,27 +47,42 @@ html, body {
 
         <div class="row">
             <div class="col-md-12">
-                <h2>Synchronization</h2>
-                <div class="progress">
-                    <div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
-                        ng-class="{'progress-bar-success': model.needBytes === 0, 'progress-bar-info': model.needBytes !== 0}"
-                        style="width: {{100 * model.inSyncBytes / model.globalBytes | number:2}}%;">
-                        {{100 * model.inSyncBytes / model.globalBytes | number:0}}%
+                <div class="panel" ng-class="{'panel-success': model.needBytes === 0, 'panel-primary': model.needBytes !== 0}">
+                    <div class="panel-heading"><h3 class="panel-title">Synchronization</h3></div>
+                    <div class="panel-body">
+                        <div class="progress">
+                            <div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
+                                ng-class="{'progress-bar-success': model.needBytes === 0, 'progress-bar-info': model.needBytes !== 0}"
+                                style="width: {{100 * model.inSyncBytes / model.globalBytes | number:2}}%;">
+                                 {{100 * model.inSyncBytes / model.globalBytes | alwaysNumber | number:0}}%
+                            </div>
+                        </div>
+                        <p ng-show="model.needBytes > 0">Need {{model.needFiles | alwaysNumber}} files, {{model.needBytes | binary}}B</p>
                     </div>
                 </div>
-                <p ng-show="model.needBytes > 0">Need {{model.needFiles | alwaysNumber}} files, {{model.needBytes | binary}}B</p>
             </div>
         </div>
 
         <div class="row">
             <div class="col-md-6">
-                <h1>Repository Status</h1>
+                <div class="panel panel-info">
+                    <div class="panel-heading"><h3 class="panel-title">Repository</h3></div>
+                    <div class="panel-body">
+                        <p>Cluster contains {{model.globalFiles | alwaysNumber}} files, {{model.globalBytes | binary}}B
+                        <span class="text-muted">(+{{model.globalDeleted | alwaysNumber}} delete records)</span></p>
 
-                <p>Cluster contains {{model.globalFiles | alwaysNumber}} files, {{model.globalBytes | binary}}B
-                <span class="text-muted">(+{{model.globalDeleted | alwaysNumber}} delete records)</span></p>
+                        <p>Local repository has {{model.localFiles | alwaysNumber}} files, {{model.localBytes | binary}}B
+                        <span class="text-muted">(+{{model.localDeleted | alwaysNumber}} delete records)</span></p>
+                    </div>
+                </div>
 
-                <p>Local repository has {{model.localFiles | alwaysNumber}} files, {{model.localBytes | binary}}B
-                <span class="text-muted">(+{{model.localDeleted | alwaysNumber}} delete records)</span></p>
+                <div class="panel panel-info">
+                    <div class="panel-heading"><h3 class="panel-title">System</h3></div>
+                    <div class="panel-body">
+                        <p>{{system.sys | binary}}B RAM allocated, {{system.alloc | binary}}B in use</p>
+                        <p>{{system.cpuPercent | alwaysNumber | natural:1}}% CPU, {{system.goroutines | alwaysNumber}} goroutines</p>
+                    </div>
+                </div>
 
                 <div ng-show="model.needFiles > 0">
                     <h2>Files to Synchronize</h2>
@@ -80,32 +95,34 @@ html, body {
                 </div>
             </div>
             <div class="col-md-6">
-                <h1>Cluster Status</h1>
-                <table class="table table-condensed">
-                    <tbody>
-                    <tr ng-repeat="(node, address) in config.nodes" ng-class="{'text-primary': !!connections[node]}">
-                        <td><abbr class="text-monospace" title="{{node}}">{{node | short}}</abbr></td>
-                        <td>
-                            <span ng-show="!!connections[node]">
-                                <span class="glyphicon glyphicon-link"></span>
-                                {{connections[node].Address}}
-                            </span>
-                            <span ng-hide="!!connections[node]">
-                                <span class="glyphicon glyphicon-cog"></span>
-                                {{address}}
-                            </span>
-                        </td>
-                        <td class="text-right">
-                            <abbr title="{{connections[node].InBytesTotal | binary}}B">{{connections[node].inbps | metric}}b/s</abbr>
-                            <span class="text-muted glyphicon glyphicon-cloud-download"></span>
-                        </td>
-                        <td class="text-right">
-                            <abbr title="{{connections[node].OutBytesTotal | binary}}B">{{connections[node].outbps | metric}}b/s</abbr>
-                            <span class="text-muted glyphicon glyphicon-cloud-upload"></span>
-                        </td>
-                    </tr>
-                    </tbody>
-                </table>
+                <div class="panel panel-info">
+                    <div class="panel-heading"><h3 class="panel-title">Cluster</h3></div>
+                    <table class="table table-condensed">
+                        <tbody>
+                        <tr ng-repeat="(node, address) in config.nodes" ng-class="{'text-primary': !!connections[node]}">
+                            <td><abbr class="text-monospace" title="{{node}}">{{node | short}}</abbr></td>
+                            <td>
+                                <span ng-show="!!connections[node]">
+                                    <span class="glyphicon glyphicon-link"></span>
+                                    {{connections[node].Address}}
+                                </span>
+                                <span ng-hide="!!connections[node]">
+                                    <span class="glyphicon glyphicon-cog"></span>
+                                    {{address}}
+                                </span>
+                            </td>
+                            <td class="text-right">
+                                <abbr title="{{connections[node].InBytesTotal | binary}}B">{{connections[node].inbps | metric}}b/s</abbr>
+                                <span class="text-muted glyphicon glyphicon-cloud-download"></span>
+                            </td>
+                            <td class="text-right">
+                                <abbr title="{{connections[node].OutBytesTotal | binary}}B">{{connections[node].outbps | metric}}b/s</abbr>
+                                <span class="text-muted glyphicon glyphicon-cloud-upload"></span>
+                            </td>
+                        </tr>
+                        </tbody>
+                    </table>
+                </div>
             </div>
         </div>
     </div>
@@ -123,7 +140,7 @@ html, body {
         <div class="modal-content">
             <div class="modal-header alert alert-danger">
                 <h4 class="modal-title">
-                <span class="glyphicon glyphicon-exclamation-sign"></span>
+                    <span class="glyphicon glyphicon-exclamation-sign"></span>
                     Connection Error
                 </h4>
             </div>

+ 31 - 0
gui_unix.go

@@ -0,0 +1,31 @@
+//+build !windows
+
+package main
+
+import (
+	"syscall"
+	"time"
+)
+
+func init() {
+	go trackCPUUsage()
+}
+
+func trackCPUUsage() {
+	var prevUsage int64
+	var prevTime = time.Now().UnixNano()
+	var rusage syscall.Rusage
+	for {
+		time.Sleep(10 * time.Second)
+		syscall.Getrusage(syscall.RUSAGE_SELF, &rusage)
+		curTime := time.Now().UnixNano()
+		timeDiff := curTime - prevTime
+		curUsage := rusage.Utime.Nano() + rusage.Stime.Nano()
+		usageDiff := curUsage - prevUsage
+		cpuUsageLock.Lock()
+		cpuUsagePercent = 100 * float64(usageDiff) / float64(timeDiff)
+		cpuUsageLock.Unlock()
+		prevTime = curTime
+		prevUsage = curUsage
+	}
+}

+ 10 - 0
main.go

@@ -10,6 +10,8 @@ import (
 	_ "net/http/pprof"
 	"os"
 	"path"
+	"runtime"
+	"runtime/debug"
 	"strconv"
 	"strings"
 	"time"
@@ -81,6 +83,14 @@ func main() {
 		os.Exit(0)
 	}
 
+	if len(os.Getenv("GOGC")) == 0 {
+		debug.SetGCPercent(25)
+	}
+
+	if len(os.Getenv("GOMAXPROCS")) == 0 {
+		runtime.GOMAXPROCS(runtime.NumCPU())
+	}
+
 	if len(opts.Debug.TraceModel) > 0 || opts.Debug.LogSource {
 		logger = log.New(os.Stderr, "", log.Lshortfile|log.Ldate|log.Ltime|log.Lmicroseconds)
 	}