diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccd0805 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor +/bin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5f5a95f --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 2e900a7..ec0f0bd 100644 --- a/README.md +++ b/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. diff --git a/client/scripts/realtimetraffic.js b/client/scripts/realtimetraffic.js index 5b291a9..24ee81f 100644 --- a/client/scripts/realtimetraffic.js +++ b/client/scripts/realtimetraffic.js @@ -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(); diff --git a/src/realtimetraffic/conn.go b/src/realtimetraffic/conn.go new file mode 100644 index 0000000..b945366 --- /dev/null +++ b/src/realtimetraffic/conn.go @@ -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() +} diff --git a/src/realtimetraffic/data.go b/src/realtimetraffic/data.go new file mode 100644 index 0000000..d56c596 --- /dev/null +++ b/src/realtimetraffic/data.go @@ -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) +} diff --git a/src/realtimetraffic/grabber.go b/src/realtimetraffic/grabber.go new file mode 100644 index 0000000..a9cd413 --- /dev/null +++ b/src/realtimetraffic/grabber.go @@ -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) + } + +} diff --git a/src/realtimetraffic/hub.go b/src/realtimetraffic/hub.go new file mode 100644 index 0000000..c030604 --- /dev/null +++ b/src/realtimetraffic/hub.go @@ -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) + } + } + } + } + } + } +} diff --git a/src/realtimetraffic/main.go b/src/realtimetraffic/main.go new file mode 100644 index 0000000..6afafef --- /dev/null +++ b/src/realtimetraffic/main.go @@ -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) + } + +} diff --git a/trafficserver/__init__.py b/trafficserver/__init__.py deleted file mode 100644 index dfa21dd..0000000 --- a/trafficserver/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import trafficserver diff --git a/trafficserver/trafficserver.py b/trafficserver/trafficserver.py deleted file mode 100644 index 2ec261f..0000000 --- a/trafficserver/trafficserver.py +++ /dev/null @@ -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) - -