mirror of
https://github.com/longsleep/realtimetraffic.git
synced 2024-11-16 08:48:33 +01:00
Rewrite of server code with Go.
This commit is contained in:
parent
ec371f324e
commit
54db38bac6
11 changed files with 573 additions and 247 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/vendor
|
||||
/bin
|
60
Makefile
Normal file
60
Makefile
Normal file
|
@ -0,0 +1,60 @@
|
|||
|
||||
PKG := realtimetraffic
|
||||
EXENAME := realtimetraffic
|
||||
|
||||
GOPATH = "$(CURDIR)/vendor:$(CURDIR)"
|
||||
SYSTEM_GOPATH := /usr/share/gocode/src/
|
||||
OUTPUT := $(CURDIR)/bin
|
||||
|
||||
DESTDIR ?= /
|
||||
BIN := $(DESTDIR)/usr/sbin
|
||||
CONF := $(DESTDIR)/$(CONFIG_PATH)
|
||||
|
||||
BUILD_ARCH := $(shell go env GOARCH)
|
||||
DIST := $(CURDIR)/dist_$(BUILD_ARCH)
|
||||
DIST_SRC := $(DIST)/src
|
||||
DIST_BIN := $(DIST)/bin
|
||||
|
||||
build: get binary
|
||||
|
||||
gopath:
|
||||
@echo GOPATH=$(GOPATH)
|
||||
|
||||
get:
|
||||
GOPATH=$(GOPATH) go get $(PKG)
|
||||
|
||||
binary:
|
||||
GOPATH=$(GOPATH) go build -o $(OUTPUT)/$(EXENAME) -ldflags '$(LDFLAGS)' $(PKG)
|
||||
|
||||
binaryrace:
|
||||
GOPATH=$(GOPATH) go build -race -o $(OUTPUT)/$(EXENAME) -ldflags '$(LDFLAGS)' $(PKG)
|
||||
|
||||
fmt:
|
||||
GOPATH=$(GOPATH) go fmt $(PKG)/...
|
||||
|
||||
test: TESTDEPS = $(shell GOPATH=$(GOPATH) go list -f '{{.ImportPath}}{{"\n"}}{{join .Deps "\n"}}' $(PKG) |grep $(PKG))
|
||||
test: get
|
||||
GOPATH=$(GOPATH) go test -i $(TESTDEPS)
|
||||
GOPATH=$(GOPATH) go test -v $(TESTDEPS)
|
||||
|
||||
clean:
|
||||
GOPATH=$(GOPATH) go clean -i $(PKG)
|
||||
rm -rf $(CURDIR)/pkg
|
||||
|
||||
distclean: clean
|
||||
rm -rf $(DIST)
|
||||
|
||||
pristine: distclean
|
||||
rm -rf vendor/*
|
||||
|
||||
$(DIST_SRC):
|
||||
mkdir -p $@
|
||||
|
||||
$(DIST_BIN):
|
||||
mkdir -p $@
|
||||
|
||||
dist_gopath: $(DIST_SRC)
|
||||
find $(SYSTEM_GOPATH) -mindepth 1 -maxdepth 1 -type d \
|
||||
-exec ln -sf {} $(DIST_SRC) \;
|
||||
|
||||
.PHONY: clean distclean pristine get build gopath binary
|
34
README.md
34
README.md
|
@ -8,46 +8,28 @@ Realtime Traffic is a Linux realtime trafic monitoring tool, graphing rx and tx
|
|||
|
||||
Dowload the software, either using Git, or grab a [ZIP](https://github.com/longsleep/realtimetraffic/archive/master.zip) and extract it somewhere.
|
||||
|
||||
You can use the client right away without installation. Just open the file client/realtimetraffic.html in any modern browser, type in a WebSocket address of a server you started somewhere and press start.
|
||||
|
||||
To install the server, make sure you have [Python](http://www.python.org) (2.5, 2.6, 2.7 tested) and [tornado](http://pypi.python.org/pypi/tornado). For Python << 2.6 you also need [simplejson](http://pypi.python.org/pypi/simplejson). Then just startup the server.
|
||||
|
||||
On Ubuntu this is simple like this:
|
||||
Getting started:
|
||||
|
||||
$ wget -O rtt.zip https://github.com/longsleep/realtimetraffic/archive/master.zip
|
||||
$ unzip rtt.zip
|
||||
$ cd realtimetraffic-master
|
||||
$ sudo apt-get install python-tornado
|
||||
$ python trafficserver/trafficserver.py
|
||||
Server running on 127.0.0.1:8088 (ssl:False) ...
|
||||
$ make
|
||||
$ ./bin/realtimetraffic
|
||||
|
||||
Now just open up your browser:
|
||||
|
||||
$ firefox http://127.0.0.1:8088/?autostart=1
|
||||
|
||||
## Getting Started
|
||||
|
||||
Startup the traffice server on a Linux machine of your choice.
|
||||
|
||||
$ python trafficserver/trafficserver.py
|
||||
|
||||
And open up the server's web page (http://yourserver:8088/).
|
||||
|
||||
See the usage information (--help) for options.
|
||||
See the usage information (-h) for options.
|
||||
|
||||
## Server usage options
|
||||
|
||||
The trafficserver is basically a Websocket server pushing traffic data to any connected client.
|
||||
|
||||
```
|
||||
Usage: trafficserver.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-l LISTEN, --listen=LISTEN
|
||||
listen address (default: [127.0.0.1:8088])
|
||||
--ssl_keyfile=FILE SSL key file
|
||||
--ssl_certfile=FILE SSL certificate file
|
||||
Usage of ./bin/realtimetraffic:
|
||||
-client="./client": Path to client directory.
|
||||
-listen="127.0.0.1:8088": Listen address.
|
||||
```
|
||||
|
||||
## Client parameters
|
||||
|
@ -66,7 +48,7 @@ This library was developed by Simon Eisenmann at [struktur AG](http://www.strukt
|
|||
|
||||
## License
|
||||
|
||||
Copyright (C) 2012 struktur AG
|
||||
Copyright (C) 2012-2014 struktur AG
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -17,10 +17,18 @@
|
|||
|
||||
(function() {
|
||||
|
||||
var add = function(a, b) {
|
||||
return a+b;
|
||||
}
|
||||
|
||||
var RealtimeTraffic = function() {
|
||||
|
||||
this.tv = 500;
|
||||
this.tv = 1000;
|
||||
this.last_data = null;
|
||||
this.history = {
|
||||
rx_bytes: [],
|
||||
tx_bytes: []
|
||||
};
|
||||
this.connection = null;
|
||||
this.initialize();
|
||||
|
||||
|
@ -33,10 +41,10 @@
|
|||
height: 300,
|
||||
width: 690,
|
||||
renderer: 'line',
|
||||
interpolation: 'linear',
|
||||
interpolation: 'basis',
|
||||
series: new Rickshaw.Series.FixedDuration([ { name: 'rx_tx_abs_diff', color: "palegreen" }, { name: 'rx_kbits', color: "mediumslateblue" }, { name: 'tx_kbits', color: "lightcoral" }], undefined, {
|
||||
timeInterval: this.tv,
|
||||
maxDataPoints: 200,
|
||||
maxDataPoints: 100,
|
||||
timeBase: new Date().getTime() / 1000
|
||||
})
|
||||
} );
|
||||
|
@ -126,14 +134,22 @@
|
|||
|
||||
connection.onmessage = function(e) {
|
||||
var data = JSON.parse(e.data);
|
||||
var interface_data = data[interf];
|
||||
var interface_data = data[data.name];
|
||||
if (typeof(interface_data) !== "undefined") {
|
||||
if (that.last_data !== null) {
|
||||
that.history.rx_bytes.push(interface_data.rx_bytes - that.last_data.rx_bytes);
|
||||
that.history.tx_bytes.push(interface_data.tx_bytes - that.last_data.tx_bytes);
|
||||
var d = {
|
||||
rx_kbits: ((interface_data.rx_bytes - that.last_data.rx_bytes) * 8 / 1024)*2,
|
||||
tx_kbits: ((interface_data.tx_bytes - that.last_data.tx_bytes) * 8 / 1024)*2
|
||||
rx_kbits: (that.history.rx_bytes.reduce(add, 0) / that.history.rx_bytes.length) * 8 / 1024,
|
||||
tx_kbits: (that.history.tx_bytes.reduce(add, 0) / that.history.tx_bytes.length) * 8 / 1024
|
||||
};
|
||||
d.rx_tx_abs_diff = Math.abs(d.rx_kbits - d.tx_kbits);
|
||||
if (that.history.rx_bytes.length > 1) {
|
||||
that.history.rx_bytes.shift();
|
||||
}
|
||||
if (that.history.tx_bytes.length > 1) {
|
||||
that.history.tx_bytes.shift();
|
||||
}
|
||||
setTimeout(function() {
|
||||
that.graph.series.addData(d);
|
||||
that.graph.render();
|
||||
|
|
136
src/realtimetraffic/conn.go
Normal file
136
src/realtimetraffic/conn.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (C) 2014 struktur AG
|
||||
* http://www.strukturag.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is baeed on ideas from the chat example of gorilla/websocket.
|
||||
* Copyright 2013 Gary Burd. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
writeWait = 10 * time.Second
|
||||
pongWait = 60 * time.Second
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
maxMessageSize = 512
|
||||
)
|
||||
|
||||
type connection struct {
|
||||
ws *websocket.Conn
|
||||
send chan []byte
|
||||
iface string
|
||||
}
|
||||
|
||||
func (c *connection) readPump() {
|
||||
defer func() {
|
||||
h.unregister <- c
|
||||
c.ws.Close()
|
||||
}()
|
||||
c.ws.SetReadLimit(maxMessageSize)
|
||||
c.ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
c.ws.SetPongHandler(func(string) error {
|
||||
c.ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
return nil
|
||||
})
|
||||
for {
|
||||
_, _, err := c.ws.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) write(mt int, payload []byte) error {
|
||||
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
return c.ws.WriteMessage(mt, payload)
|
||||
}
|
||||
|
||||
func (c *connection) writePump() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.ws.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
if !ok {
|
||||
c.write(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
if err := c.write(websocket.TextMessage, message); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "Method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
// Read request details.
|
||||
r.ParseForm()
|
||||
iface := r.FormValue("if")
|
||||
if iface == "" {
|
||||
iface = "eth0"
|
||||
}
|
||||
|
||||
ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
|
||||
if _, ok := err.(websocket.HandshakeError); ok {
|
||||
http.Error(w, "Not a websocket handshake", 400)
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
c := &connection{send: make(chan []byte, 256), ws: ws, iface: iface}
|
||||
h.register <- c
|
||||
go c.writePump()
|
||||
c.readPump()
|
||||
}
|
38
src/realtimetraffic/data.go
Normal file
38
src/realtimetraffic/data.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (C) 2014 struktur AG
|
||||
* http://www.strukturag.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type interfacedata struct {
|
||||
data interface{}
|
||||
iface string
|
||||
}
|
||||
|
||||
func (i *interfacedata) set(iface string, data interface{}) {
|
||||
i.data = map[string]interface{}{
|
||||
iface: data,
|
||||
"name": iface,
|
||||
}
|
||||
i.iface = iface
|
||||
}
|
||||
|
||||
func (i *interfacedata) encode() ([]byte, error) {
|
||||
return json.Marshal(i.data)
|
||||
}
|
140
src/realtimetraffic/grabber.go
Normal file
140
src/realtimetraffic/grabber.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2014 struktur AG
|
||||
* http://www.strukturag.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var grabkeys = []string{
|
||||
"collisions",
|
||||
"multicast",
|
||||
"rx_bytes",
|
||||
"rx_compressed",
|
||||
"rx_crc_errors",
|
||||
"rx_dropped",
|
||||
"rx_errors",
|
||||
"rx_fifo_errors",
|
||||
"rx_frame_errors",
|
||||
"rx_length_errors",
|
||||
"rx_missed_errors",
|
||||
"rx_over_errors",
|
||||
"rx_packets",
|
||||
"tx_aborted_errors",
|
||||
"tx_bytes",
|
||||
"tx_carrier_errors",
|
||||
"tx_compressed",
|
||||
"tx_dropped",
|
||||
"tx_errors",
|
||||
"tx_fifo_errors",
|
||||
"tx_heartbeat_errors",
|
||||
"tx_packets",
|
||||
"tx_window_errors",
|
||||
}
|
||||
|
||||
type grabber struct {
|
||||
path string
|
||||
iface string
|
||||
quit chan bool
|
||||
running bool
|
||||
count uint
|
||||
}
|
||||
|
||||
func newGrabber(iface string) *grabber {
|
||||
return &grabber{
|
||||
iface: iface,
|
||||
path: fmt.Sprintf("/sys/class/net/%s/statistics", iface),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *grabber) grab() {
|
||||
|
||||
data := &interfacedata{}
|
||||
statistics := map[string]interface{}{}
|
||||
value := make([]byte, 100)
|
||||
|
||||
var file *os.File
|
||||
var err error
|
||||
for _, key := range grabkeys {
|
||||
file, err = os.Open(path.Join(g.path, key))
|
||||
if err == nil {
|
||||
count, err := file.Read(value)
|
||||
if err == nil {
|
||||
statistics[key], err = strconv.ParseInt(strings.TrimSpace(string(value[:count])), 10, 64)
|
||||
if err != nil {
|
||||
log.Println("Failed to process data", err, value[:count], count)
|
||||
}
|
||||
} else {
|
||||
log.Println("Failed to read data", err)
|
||||
}
|
||||
file.Close()
|
||||
} else {
|
||||
log.Println("Failed to load data", err)
|
||||
}
|
||||
}
|
||||
|
||||
data.set(g.iface, statistics)
|
||||
h.broadcast <- data
|
||||
|
||||
}
|
||||
|
||||
func (g *grabber) start() {
|
||||
|
||||
g.count++
|
||||
if g.running {
|
||||
return
|
||||
}
|
||||
|
||||
g.quit = make(chan bool)
|
||||
g.running = true
|
||||
|
||||
go func() {
|
||||
fmt.Printf("Grabbing started: %s\n", g.iface)
|
||||
ticker := time.NewTicker(1000 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
g.grab()
|
||||
case <-g.quit:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func (g *grabber) stop() {
|
||||
|
||||
if !g.running {
|
||||
return
|
||||
}
|
||||
|
||||
g.count--
|
||||
if g.count == 0 {
|
||||
g.running = false
|
||||
close(g.quit)
|
||||
fmt.Printf("Grabbing stopped: %s\n", g.iface)
|
||||
}
|
||||
|
||||
}
|
94
src/realtimetraffic/hub.go
Normal file
94
src/realtimetraffic/hub.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (C) 2014 struktur AG
|
||||
* http://www.strukturag.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is baeed on ideas from the chat example of gorilla/websocket.
|
||||
* Copyright 2013 Gary Burd. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package main
|
||||
|
||||
type hub struct {
|
||||
grabbers map[string]*grabber
|
||||
connections map[*connection]bool
|
||||
broadcast chan *interfacedata
|
||||
register chan *connection
|
||||
unregister chan *connection
|
||||
}
|
||||
|
||||
var h = hub{
|
||||
broadcast: make(chan *interfacedata),
|
||||
register: make(chan *connection),
|
||||
unregister: make(chan *connection),
|
||||
connections: make(map[*connection]bool),
|
||||
grabbers: make(map[string]*grabber),
|
||||
}
|
||||
|
||||
func (h *hub) run() {
|
||||
|
||||
var eg *grabber
|
||||
var ok bool
|
||||
|
||||
for {
|
||||
select {
|
||||
case c := <-h.register:
|
||||
h.connections[c] = true
|
||||
if eg, ok = h.grabbers[c.iface]; !ok {
|
||||
eg = newGrabber(c.iface)
|
||||
h.grabbers[c.iface] = eg
|
||||
}
|
||||
eg.start()
|
||||
case c := <-h.unregister:
|
||||
delete(h.connections, c)
|
||||
close(c.send)
|
||||
if eg, ok = h.grabbers[c.iface]; ok {
|
||||
eg.stop()
|
||||
}
|
||||
case d := <-h.broadcast:
|
||||
for c := range h.connections {
|
||||
if c.iface == d.iface {
|
||||
if m, err := d.encode(); err == nil {
|
||||
select {
|
||||
case c.send <- m:
|
||||
default:
|
||||
close(c.send)
|
||||
delete(h.connections, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
73
src/realtimetraffic/main.go
Normal file
73
src/realtimetraffic/main.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (C) 2014 struktur AG
|
||||
* http://www.strukturag.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var templates *template.Template
|
||||
|
||||
func serveClient(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.Error(w, "Not found", 404)
|
||||
return
|
||||
}
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "Method nod allowed", 405)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
err := templates.ExecuteTemplate(w, "realtimetraffic.html", nil)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
client := flag.String("client", path.Join(cwd, "client"), "Full path to client directory.")
|
||||
addr := flag.String("listen", "127.0.0.1:8088", "Listen address.")
|
||||
|
||||
templates, err = template.ParseGlob(path.Join(*client, "*.html"))
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load templates: ", err)
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
go h.run()
|
||||
http.HandleFunc("/", serveClient)
|
||||
http.HandleFunc("/realtimetraffic", serveWs)
|
||||
http.Handle("/css/", http.FileServer(http.Dir(*client)))
|
||||
http.Handle("/scripts/", http.FileServer(http.Dir(*client)))
|
||||
http.Handle("/img/", http.FileServer(http.Dir(*client)))
|
||||
http.Handle("/favicon.ico", http.FileServer(http.Dir(*client)))
|
||||
|
||||
err = http.ListenAndServe(*addr, nil)
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
import trafficserver
|
|
@ -1,214 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
"""
|
||||
Copyright (C) 2012 struktur AG
|
||||
http://www.strukturag.com
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import tornado.httpserver
|
||||
import tornado.websocket
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
# Globals
|
||||
dataGrabbers = {}
|
||||
dataGrabbed = {}
|
||||
CLIENT_ROOT = os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "client")))
|
||||
|
||||
|
||||
class Grabber(object):
|
||||
|
||||
keys = [
|
||||
"collisions",
|
||||
"multicast",
|
||||
"rx_bytes",
|
||||
"rx_compressed",
|
||||
"rx_crc_errors",
|
||||
"rx_dropped",
|
||||
"rx_errors",
|
||||
"rx_fifo_errors",
|
||||
"rx_frame_errors",
|
||||
"rx_length_errors",
|
||||
"rx_missed_errors",
|
||||
"rx_over_errors",
|
||||
"rx_packets",
|
||||
"tx_aborted_errors",
|
||||
"tx_bytes",
|
||||
"tx_carrier_errors",
|
||||
"tx_compressed",
|
||||
"tx_dropped",
|
||||
"tx_errors",
|
||||
"tx_fifo_errors",
|
||||
"tx_heartbeat_errors",
|
||||
"tx_packets",
|
||||
"tx_window_errors"
|
||||
]
|
||||
|
||||
def __init__(self, interface):
|
||||
self.interface = interface
|
||||
self.base = "/sys/class/net/%s/statistics" % interface
|
||||
|
||||
def grab(self):
|
||||
|
||||
data = {}
|
||||
for k in self.keys:
|
||||
data[k] = self.read(k)
|
||||
|
||||
dataGrabbed[self.interface]=data
|
||||
|
||||
def read(self, key):
|
||||
data = 0
|
||||
fn = os.path.join(self.base, key)
|
||||
if os.path.exists(fn):
|
||||
fp = file(fn, "rb")
|
||||
try:
|
||||
data = fp.read()
|
||||
finally:
|
||||
fp.close()
|
||||
data = int(data.strip())
|
||||
return data
|
||||
|
||||
|
||||
class WSHandler(tornado.websocket.WebSocketHandler):
|
||||
|
||||
dataSender = None
|
||||
dataInterface = None
|
||||
|
||||
def open(self):
|
||||
|
||||
interface = self.dataInterface = self.get_argument("if", "eth0").lower()
|
||||
|
||||
grabber = dataGrabbers.get(interface, None)
|
||||
if grabber is None:
|
||||
# create new grabber
|
||||
print >>sys.stdout, "Starting grabber for %s." % interface
|
||||
callback = Grabber(interface)
|
||||
grabber = tornado.ioloop.PeriodicCallback(callback.grab, 240)
|
||||
grabber.start()
|
||||
dataGrabbers[interface]=[1, grabber]
|
||||
else:
|
||||
dataGrabbers[interface][0] = grabber[0]+1
|
||||
|
||||
sender = self.dataSender = tornado.ioloop.PeriodicCallback(self.doSendData, 500)
|
||||
sender.start()
|
||||
|
||||
def on_message(self, message):
|
||||
pass
|
||||
|
||||
def on_close(self):
|
||||
|
||||
self.dataSender.stop()
|
||||
self.dataSender = None
|
||||
|
||||
grabber = dataGrabbers.get(self.dataInterface, None)
|
||||
if grabber is not None:
|
||||
dataGrabbers[self.dataInterface][0] = grabber[0]-1
|
||||
if grabber[0] <= 0:
|
||||
print >>sys.stdout, "Stopping grabber for %s." % self.dataInterface
|
||||
grabber[1].stop()
|
||||
del dataGrabbers[self.dataInterface]
|
||||
|
||||
def doSendData(self):
|
||||
|
||||
data = {
|
||||
self.dataInterface: dataGrabbed.get(self.dataInterface, {})
|
||||
}
|
||||
self.write_message(json.dumps(data))
|
||||
|
||||
|
||||
class ClientHandler(tornado.web.RequestHandler):
|
||||
|
||||
def get(self, filename):
|
||||
|
||||
if not filename or filename == "index.html":
|
||||
filename = "realtimetraffic.html"
|
||||
|
||||
fn = os.path.abspath(os.path.join(CLIENT_ROOT, os.path.normpath(filename)))
|
||||
if not fn.startswith(CLIENT_ROOT):
|
||||
raise tornado.web.HTTPError(404)
|
||||
if not os.path.isfile(fn):
|
||||
raise tornado.web.HTTPError(404)
|
||||
fp = file(fn, "rb")
|
||||
try:
|
||||
self.write(fp.read())
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
|
||||
def main(listen="127.0.0.1:8088"):
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option("-l", "--listen", dest="listen", help="listen address (default: [%s])" % listen, default=listen)
|
||||
parser.add_option("--ssl_keyfile", dest="ssl_keyfile", help="SSL key file", metavar="FILE")
|
||||
parser.add_option("--ssl_certfile", dest="ssl_certfile", help="SSL certificate file", metavar="FILE")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if ":" in options.listen:
|
||||
address, port = options.listen.split(":", 1)
|
||||
port = int(port)
|
||||
listen = options.listen
|
||||
else:
|
||||
address = options.listen
|
||||
port = 8088
|
||||
listen = "%s:%s" % (address, port)
|
||||
|
||||
application = tornado.web.Application([
|
||||
(r'^/realtimetraffic$', WSHandler),
|
||||
(r'^/css/(.*)$', tornado.web.StaticFileHandler, {'path': os.path.join(CLIENT_ROOT, 'css')}),
|
||||
(r'^/scripts/(.*)$', tornado.web.StaticFileHandler, {'path': os.path.join(CLIENT_ROOT, 'scripts')}),
|
||||
(r'^/img/(.*)$', tornado.web.StaticFileHandler, {'path': os.path.join(CLIENT_ROOT, 'img')}),
|
||||
(r'^/(.*)$', ClientHandler)
|
||||
], debug=False, static_path=CLIENT_ROOT)
|
||||
|
||||
params = {}
|
||||
ssl = False
|
||||
if options.ssl_keyfile and options.ssl_certfile:
|
||||
if not os.path.isfile(options.ssl_keyfile):
|
||||
print >>sys.stderr, "SSL key file not found: %s" % options.ssl_keyfile
|
||||
return 1
|
||||
if not os.path.isfile(options.ssl_certfile):
|
||||
print >>sys.stderr, "SSL certificate file not found: %s" % options.ssl_certfile
|
||||
return 1
|
||||
params["ssl_options"] = {
|
||||
"keyfile": options.ssl_keyfile,
|
||||
"certfile": options.ssl_certfile
|
||||
}
|
||||
ssl = True
|
||||
|
||||
http_server = tornado.httpserver.HTTPServer(application, **params)
|
||||
http_server.listen(port=port, address=address)
|
||||
print >>sys.stdout, "Server running on %s (ssl:%r) ..." % (listen, ssl)
|
||||
try:
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
print >>sys.stdout, "Server stopped."
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
status = main()
|
||||
sys.exit(status)
|
||||
|
||||
|
Loading…
Reference in a new issue