-
Notifications
You must be signed in to change notification settings - Fork 0
/
go_watcher.go
366 lines (305 loc) · 8.91 KB
/
go_watcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
package go_watcher
import (
"context"
"encoding/json"
"html/template"
"net/http"
"runtime"
"time"
"github.com/gorilla/websocket"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
)
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
)
// use default options
var upgrader = websocket.Upgrader{}
var Stats PC_stats
var PollPeriod time.Duration
type PC_stats struct {
CPU_Load float64 `json:"cpu_load"`
Mem_Load float64 `json:"mem_load"`
Goroutines int `json:"goroutines"`
Timestamp time.Time `json:"timestamp"`
}
// Start starts 3 Goroutines that update the Global Variable "Stats", each pollPeriod
func Start(pollPeriod time.Duration) error {
PollPeriod = pollPeriod
// Starte die Goroutines welche die Daten holen
go GetCPULoad(&Stats, pollPeriod)
go GetMemLoad(&Stats, pollPeriod)
go GetGoroutines(&Stats, pollPeriod)
return nil
}
// GetMemLoad changes PC_Stats.Mem_Load each interval.
//
// This goroutine also writes the Timestamp to PC_Stats.
func GetMemLoad(Stats *PC_stats, interval time.Duration) {
ticker := time.NewTicker(interval)
for {
select {
case <-ticker.C:
v, err := mem.VirtualMemory()
if err != nil {
panic(err)
}
Stats.Mem_Load = v.UsedPercent
Stats.Timestamp = time.Now()
}
}
}
// GetCPULoad changes PC_StatsCPU_Load each interval.
func GetCPULoad(Stats *PC_stats, interval time.Duration) {
ticker := time.NewTicker(interval)
for {
select {
case <-ticker.C:
load, err := cpu.Percent(time.Second*0, false)
if err != nil {
panic(err)
}
Stats.CPU_Load = load[0]
}
}
}
// GetCPULoad changes PC_StatsCPU_Load each interval.
func GetGoroutines(Stats *PC_stats, interval time.Duration) {
ticker := time.NewTicker(interval)
for {
select {
case <-ticker.C:
goes := runtime.NumGoroutine()
Stats.Goroutines = goes
}
}
}
// MessageReceiver is needed to listen on the Closes from the Client side.
//
// If you don´t listen to those messages, the Programm will try to write,
// on a dead connection and fail.
func MessageReceiver(ctx context.Context, conn *websocket.Conn, close chan bool) {
defer func() {
// logger.Debug("Listen Function exited.")
}()
conn.SetReadLimit(maxMessageSize)
conn.SetReadDeadline(time.Now().Add(pongWait))
conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
// Technically, this does not need to be a loop.
for {
_, _, err := conn.ReadMessage()
if err != nil {
// logger.Info("Websocket Connection closed.")
close <- true
return
}
}
}
// MessageWriter writes to the Connection, at specified interval.
//
// If the connection is closed on the client side, the goroutine is notified
// via the C_close Channel and returns.
// Additionally it regularly writes a PingMessage to the connection.
func MessageWriter(ctx context.Context, conn *websocket.Conn, poll *time.Ticker, pong *time.Ticker) {
defer func() {
// logger.Debug("Write Function exited.")
}()
for {
select {
case <-poll.C:
conn.SetWriteDeadline(time.Now().Add(writeWait))
w, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
// logger.Warn("c.NextWriter did not work", err)
}
json, err := json.Marshal(Stats)
if err != nil {
// logger.Fatalln("Marshalling did not work", err)
}
w.Write(json)
if err := w.Close(); err != nil {
// logger.Warn("io.Writer Close did not work", err)
}
case <-pong.C:
conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
// logger.Warn("c.WriteMessage did not work", err)
}
case <-ctx.Done():
return
}
}
}
// SendStatusUpdates implements to Websocket Logik.
// So far I could not come up with an Idea how to stop MessageReceiver.
// Dont know how if thats bad...
func SendUpdates(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
// logger.Warn("Error while upgrading", err)
return
}
// logger.Info("WebsocketConnection established by:", r.RemoteAddr)
// Used to stop MessageWriter
ctx, cancelfunc := context.WithCancel(r.Context())
// Channel for PongMessages
pong := time.NewTicker(pingPeriod)
// Channel for Polling the CPU/Mem Stats
poll := time.NewTicker(PollPeriod)
// Channel to notify this function if Conn closed on Client Side
client_close := make(chan bool)
defer func() {
cancelfunc()
pong.Stop()
poll.Stop()
conn.Close()
close(client_close)
}()
go MessageReceiver(ctx, conn, client_close)
go MessageWriter(ctx, conn, poll, pong)
// Blocking until MessageReceiver gets notified about Closed Connection.
<-client_close
}
// SendTemplate sends a HMTL which creates a Websocket Connection an updates Graphs.
// To function the SendUpdate function needs to added as *"echo"* and located relativ to
// this Path. For example:
// http.HandleFunc("/echo", SendUpdates)
// http.HandleFunc("/", SendTemplate)
func SendTemplate(w http.ResponseWriter, r *http.Request) {
// Das hier läuft zweimail weil, /favicon.ico auch hierher geroutete wird.
// logger.Info("Connection", r.RemoteAddr)
HomeTemplate.Execute(w, nil)
}
var HomeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="icon" href="data:,">
</head>
<body>
<div id="chart_div"></div>
<div id="chart_mem"></div>
<div id="chart_go"></div>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script>
var data;
var chart;
var ws_data;
var index = 0;
var mem_data;
var mem_chart;
var mem_ws_data;
var mem_index = 0;
var go_data;
var go_chart;
var go_ws_data;
var go_index = 0;
// create options object with titles, colors, etc.
let options = {
title: "CPU Usage",
hAxis: {
title: "Time"
},
vAxis: {
title: "Usage"
}
};
// create options object with titles, colors, etc.
let mem_options = {
title: "Memory Usage",
hAxis: {
title: "Time"
},
vAxis: {
title: "Usage"
}
};
// create options object with titles, colors, etc.
let go_options = {
title: "Num Goroutines",
hAxis: {
title: "Time"
},
vAxis: {
title: "Usage"
}
};
// Das hier weils wichtig ist ob https oder nicht.
// Browser erlauben KEIN downgrad also https zu ws!
if (location.protocol === 'https:'){
ws = new WebSocket("wss://" + document.location.host + document.location.pathname + "echo");
} else {
ws = new WebSocket("ws://" + document.location.host + document.location.pathname + "echo");
}
ws.onopen = function(evt) {
console.log("OPEN");
}
ws.onclose = function(evt) {
document.getElementById("Load").innerText = "Closed by Server"
console.log("CLOSE");
}
// Listen for messages
ws.addEventListener('message', function (event) {
console.log('Message from server ', JSON.parse(event.data));
ws_data = JSON.parse(event.data)
data.addRow([index, ws_data.cpu_load]);
chart.draw(data, options);
index++;
mem_data.addRow([mem_index, ws_data.mem_load]);
mem_chart.draw(mem_data, mem_options);
mem_index++;
go_data.addRow([go_index, ws_data.goroutines]);
go_chart.draw(go_data, go_options);
go_index++;
});
ws.onerror = function(evt) {
document.getElementById("Load").innerText = "Erro occured"
console.log("ERROR: " + evt);
}
// load current chart package
google.charts.load("current", {
packages: ["corechart", "line"]
});
// set callback function when api loaded
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
// create data object with default value
data = google.visualization.arrayToDataTable([
["Year", "CPU Usage"],
[0, 0]
]);
// create data object with default value
mem_data = google.visualization.arrayToDataTable([
["Year", "Mem Usage"],
[0, 100]
]);
// create data object with default value
go_data = google.visualization.arrayToDataTable([
["Year", "Goroutines"],
[0, 25]
]);
chart = new google.visualization.LineChart(
document.getElementById("chart_div")
);
chart.draw(data, options);
mem_chart = new google.visualization.LineChart(
document.getElementById("chart_mem")
);
mem_chart.draw(mem_data, options);
go_chart = new google.visualization.LineChart(
document.getElementById("chart_go")
);
go_chart.draw(mem_data, options);
}
</script>
</body>
</html>
`))