quick ugly performance monitor

master
codeskyblue 8 years ago
parent 3570f78f69
commit 4105d99628

@ -149,10 +149,10 @@ type Process struct {
*FSM `json:"-"`
Program `json:"program"`
cmd *kexec.KCommand
Stdout *QuickLossBroadcastWriter
Stderr *QuickLossBroadcastWriter
Output *QuickLossBroadcastWriter
OutputFile *os.File
Stdout *QuickLossBroadcastWriter `json:"-"`
Stderr *QuickLossBroadcastWriter `json:"-"`
Output *QuickLossBroadcastWriter `json:"-"`
OutputFile *os.File `json:"-"`
stopC chan syscall.Signal
retryLeft int
Status string `json:"status"`

@ -191,6 +191,7 @@
<script src="/res/js/moment.min.js"></script>
<script src="/res/js/underscore-min.js"></script>
<script src="/res/js/vue-1.0.min.js"></script>
<script src="/res/js/common.js"></script>
<script src="/res/js/index.js"></script>
<script type="text/javascript">
$(function() {

@ -0,0 +1,43 @@
/* Javascript */
function pathJoin(parts, sep) {
var separator = sep || '/';
var replace = new RegExp(separator + '{1,}', 'g');
return parts.join(separator).replace(replace, separator);
}
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = decodeURI(window.location.search).substr(1).match(reg);
if (r != null) return r[2].replace(/\+/g, ' ');
return null;
}
function newWebsocket(pathname, opts) {
var wsProtocol = location.protocol == "https:" ? "wss" : "ws";
var ws = new WebSocket(wsProtocol + "://" + location.host + pathname);
opts = opts || {};
ws.onopen = opts.onopen || function(evt) {
console.log("WS OPEN", pathname);
}
ws.onclose = opts.onclose || function(evt) {
console.log("CLOSE");
ws = null;
}
ws.onmessage = opts.onmessage || function(evt) {
console.log("response:" + evt.data);
}
ws.onerror = function(evt) {
console.error("error:", evt.data);
}
return ws;
}
function formatBytes(value) {
var bytes = parseFloat(value);
if (bytes < 0) return "-";
else if (bytes < 1024) return bytes + " B";
else if (bytes < 1048576) return (bytes / 1024).toFixed(0) + " KB";
else if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB";
else return (bytes / 1073741824).toFixed(1) + " GB";
}

@ -1,37 +1,4 @@
/* Javascript */
function pathJoin(parts, sep) {
var separator = sep || '/';
var replace = new RegExp(separator + '{1,}', 'g');
return parts.join(separator).replace(replace, separator);
}
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = decodeURI(window.location.search).substr(1).match(reg);
if (r != null) return r[2].replace(/\+/g, ' ');
return null;
}
function newWebsocket(pathname, opts) {
var ws = new WebSocket(wsProtocol + "://" + location.host + pathname);
opts = opts || {};
ws.onopen = opts.onopen || function(evt) {
console.log("WS OPEN", pathname);
}
ws.onclose = opts.onclose || function(evt) {
console.log("CLOSE");
ws = null;
}
ws.onmessage = opts.onmessage || function(evt) {
console.log("response:" + evt.data);
}
ws.onerror = function(evt) {
console.error("error:", evt.data);
}
return ws;
}
var wsProtocol = location.protocol == "https:" ? "wss" : "ws";
/* index.js */
var W = {};
var testPrograms = [{

@ -3,14 +3,43 @@ var vm = new Vue({
el: '#app',
data: {
name: name,
pid: '-',
}
});
function formatBytes(value) {
var bytes = parseFloat(value);
if (bytes < 0) return "-";
else if (bytes < 1024) return bytes + " B";
else if (bytes < 1048576) return (bytes / 1024).toFixed(0) + " KB";
else if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB";
else return (bytes / 1073741824).toFixed(1) + " GB";
}
var ws = newWebsocket('/ws/perfs/' + name, {
onopen: function(evt) {
console.log(evt);
},
onmessage: function(evt) {
var data = JSON.parse(evt.data);
vm.pid = data.pid;
console.log("pid", data.pid, data); //evt.data.pid);
if (memData && data.mem && data.mem.rss) {
memData.push({
value: [new Date(), data.mem.rss],
})
if (memData.length > 10) {
memData.shift();
}
chartMem.setOption({
series: [{
data: memData,
}]
});
}
if (cpuData && data.cpu !== undefined) {
cpuData.push({
value: [new Date(), data.cpu],
})
if (cpuData.length > 10) {
cpuData.shift();
}
chartCpu.setOption({
series: [{
data: cpuData,
}]
})
}
}
})

@ -37,7 +37,7 @@
<div class="col-md-12">
<h1>{{name}}</h1>
<p>
pid: 11234
pid: {{pid}}
<br> status: running
<br> time: 2016/09/07 12:00:00
</p>
@ -56,6 +56,7 @@
<!-- <script src="/res/js/underscore-min.js"></script> -->
<script src="/res/js/vue-1.0.min.js"></script>
<script src="/res/js/echarts.min.js"></script>
<script src="/res/js/common.js"></script>
<script src="/res/js/settings.js"></script>
<script type="text/javascript">
// 基于准备好的dom初始化echarts实例
@ -63,16 +64,7 @@
var chartMem = echarts.init(document.getElementById('chart-mem'));
// 指定图表的配置项和数据
var baseTime = new Date().getTime();
var i = 0;
function randomData() {
i += 1;
return {
value: [baseTime + i * 1000, Math.random() * 100]
}
}
var data = [];
var cpuData = [];
var option = {
title: {
@ -85,12 +77,12 @@
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
params = params[0];
// console.log(params)
var date = new Date(params.value[0]);
return date + date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate() + ' : ' + params.value[1];
},
// formatter: function(params) {
// params = params[0];
// console.log(params)
// var date = new Date(params.value[0]);
// return date + date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate() + ' : ' + params.value[1];
// },
axisPointer: {
animation: false
}
@ -112,7 +104,7 @@
series: [{
name: 'Total',
type: 'line',
data: data,
data: cpuData,
animation: false,
smooth: true,
areaStyle: {
@ -121,24 +113,22 @@
}]
}
for (var i = 0; i < 100; i++) {
data.push(randomData());
}
// 使用刚指定的配置项和数据显示图表。
chartCpu.setOption(option);
var timeTicket = setInterval(function() {
for (var i = 0; i < 1; i++) {
data.shift();
data.push(randomData());
}
chartCpu.setOption({
series: [{
data: data
}]
});
}, 1000);
// var timeTicket = setInterval(function() {
// for (var i = 0; i < 1; i++) {
// data.shift();
// data.push(randomData());
// }
// chartCpu.setOption({
// series: [{
// data: data
// }]
// });
// }, 1000);
var memData = [];
var option = {
title: {
text: 'Memory'
@ -149,14 +139,15 @@
params = params[0];
// console.log(params)
var date = new Date(params.value[0]);
return date + date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate() + ' : ' + params.value[1];
return formatBytes(params.value[1]);
// return date + date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate() + ' : ' + params.value[1];
},
axisPointer: {
animation: false
}
},
legend: {
data: ['Total']
data: ['RSS']
},
xAxis: {
type: 'time',
@ -170,9 +161,9 @@
},
},
series: [{
name: 'Total',
name: 'RSS',
type: 'line',
data: data,
data: memData,
animation: false,
smooth: true,
areaStyle: {

@ -20,6 +20,7 @@ import (
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/qiniu/log"
"github.com/shirou/gopsutil/process"
)
var defaultConfigDir string
@ -285,7 +286,7 @@ func (s *Supervisor) hReload(w http.ResponseWriter, r *http.Request) {
}
}
func (s *Supervisor) hGetProgram(w http.ResponseWriter, r *http.Request) {
func (s *Supervisor) hGetProgramList(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(s.procs())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@ -295,6 +296,23 @@ func (s *Supervisor) hGetProgram(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
func (s *Supervisor) hGetProgram(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
proc, ok := s.procMap[name]
if !ok {
s.renderJSON(w, JSONResponse{
Status: 1,
Value: "program not exists",
})
return
} else {
s.renderJSON(w, JSONResponse{
Status: 0,
Value: proc,
})
}
}
func (s *Supervisor) hAddProgram(w http.ResponseWriter, r *http.Request) {
retries, err := strconv.Atoi(r.FormValue("retries"))
if err != nil {
@ -438,6 +456,47 @@ func (s *Supervisor) wsLog(w http.ResponseWriter, r *http.Request) {
}
}
// Performance
func (s *Supervisor) wsPerf(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
name := mux.Vars(r)["name"]
proc, ok := s.procMap[name]
if !ok {
log.Println("No such process")
// TODO: raise error here?
return
}
if proc.cmd == nil || proc.cmd.Process == nil {
log.Println("process not running")
return
}
var pid = proc.cmd.Process.Pid
ps, err := process.NewProcess(int32(pid))
if err != nil {
log.Println(err)
return
}
for {
mstat, _ := ps.MemoryInfo()
pcpu, _ := ps.Percent(300 * time.Millisecond)
err = c.WriteJSON(map[string]interface{}{
"pid": pid,
"mem": mstat,
"cpu": pcpu,
})
if err != nil {
break
}
time.Sleep(500 * time.Millisecond)
}
}
func (s *Supervisor) Close() {
for _, proc := range s.procMap {
s.stopAndWait(proc.Name)
@ -482,13 +541,15 @@ func newSupervisorHandler() (hdlr http.Handler, err error) {
r.HandleFunc("/api/shutdown", suv.hShutdown).Methods("POST")
r.HandleFunc("/api/reload", suv.hReload).Methods("POST")
r.HandleFunc("/api/programs", suv.hGetProgram).Methods("GET")
r.HandleFunc("/api/programs", suv.hGetProgramList).Methods("GET")
r.HandleFunc("/api/programs/{name}", suv.hGetProgram).Methods("GET")
r.HandleFunc("/api/programs", suv.hAddProgram).Methods("POST")
r.HandleFunc("/api/programs/{name}/start", suv.hStartProgram).Methods("POST")
r.HandleFunc("/api/programs/{name}/stop", suv.hStopProgram).Methods("POST")
r.HandleFunc("/ws/events", suv.wsEvents)
r.HandleFunc("/ws/logs/{name}", suv.wsLog)
r.HandleFunc("/ws/perfs/{name}", suv.wsPerf)
return r, nil
}

Loading…
Cancel
Save