Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

added realtime graphical statistics to websocket chat example

  • Loading branch information...
commit e95128a8bbe6b6bc633ac9cc46233960735ed8ba 1 parent 8cb7e5f
Flavio Grossi flaviogrossi authored
4 README.rst
View
@@ -470,3 +470,7 @@ Thanks to (in no particular order):
- `Jeethu Rao <https://github.com/jeethu>`_
- Minor bugfixes and patches
+
+- `Flavio Grossi <https://github.com/flaviogrossi>`_
+
+ - Minor code fixes and websockets chat statistics example
75 demos/websocket/chat/chatdemo.py
View
@@ -18,21 +18,30 @@
Authentication, error handling, etc are left as an exercise for the reader :)
"""
-import cyclone.escape
-import cyclone.web
-import cyclone.websocket
import os.path
import uuid
import sys
+import time
+from collections import defaultdict
+
from twisted.python import log
-from twisted.internet import reactor
+from twisted.internet import reactor, task
+
+import cyclone.escape
+import cyclone.web
+import cyclone.websocket
class Application(cyclone.web.Application):
def __init__(self):
+
+ stats = Stats()
+
handlers = [
- (r"/", MainHandler),
- (r"/chatsocket", ChatSocketHandler),
+ (r"/", MainHandler, dict(stats=stats)),
+ (r"/stats", StatsPageHandler),
+ (r"/statssocket", StatsSocketHandler, dict(stats=stats)),
+ (r"/chatsocket", ChatSocketHandler, dict(stats=stats)),
]
settings = dict(
cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
@@ -45,7 +54,11 @@ def __init__(self):
class MainHandler(cyclone.web.RequestHandler):
+ def initialize(self, stats):
+ self.stats = stats
+
def get(self):
+ self.stats.newVisit()
self.render("index.html", messages=ChatSocketHandler.cache)
@@ -54,11 +67,16 @@ class ChatSocketHandler(cyclone.websocket.WebSocketHandler):
cache = []
cache_size = 200
+ def initialize(self, stats):
+ self.stats = stats
+
def connectionMade(self):
ChatSocketHandler.waiters.add(self)
+ self.stats.newChatter()
def connectionLost(self, reason):
ChatSocketHandler.waiters.remove(self)
+ self.stats.lostChatter()
@classmethod
def update_cache(cls, chat):
@@ -87,6 +105,50 @@ def messageReceived(self, message):
ChatSocketHandler.update_cache(chat)
ChatSocketHandler.send_updates(chat)
+class StatsSocketHandler(cyclone.websocket.WebSocketHandler):
+ def initialize(self, stats):
+ self.stats = stats
+
+ self._updater = task.LoopingCall(self._sendData)
+
+ def connectionMade(self):
+ self._updater.start(2)
+
+ def connectionLost(self, reason):
+ self._updater.stop()
+
+ def _sendData(self):
+ data = dict(visits=self.stats.todaysVisits(),
+ chatters=self.stats.chatters)
+ self.sendMessage(cyclone.escape.json_encode(data))
+
+
+class Stats(object):
+ def __init__(self):
+ self.visits = defaultdict(int)
+ self.chatters = 0
+
+ def todaysVisits(self):
+ today = time.localtime()
+ key = time.strftime('%Y%m%d', today)
+ return self.visits[key]
+
+ def newChatter(self):
+ self.chatters += 1
+
+ def lostChatter(self):
+ self.chatters -= 1
+
+ def newVisit(self):
+ today = time.localtime()
+ key = time.strftime('%Y%m%d', today)
+ self.visits[key] += 1
+
+
+class StatsPageHandler(cyclone.web.RequestHandler):
+ def get(self):
+ self.render("stats.html")
+
def main():
reactor.listenTCP(8888, Application())
@@ -96,3 +158,4 @@ def main():
if __name__ == "__main__":
log.startLogging(sys.stdout)
main()
+
9 demos/websocket/chat/static/chat.js
View
@@ -30,6 +30,10 @@ $(document).ready(function() {
updater.start();
});
+$(window).unload(function() {
+ updater.stop()
+});
+
function newMessage(form) {
var message = form.formToDict();
updater.socket.send(JSON.stringify(message));
@@ -61,6 +65,11 @@ var updater = {
}
},
+ stop: function() {
+ updater.socket.close();
+ updater.socket = null;
+ },
+
showMessage: function(message) {
var existing = $("#m" + message.id);
if (existing.length > 0) return;
3  demos/websocket/chat/templates/index.html
View
@@ -22,6 +22,9 @@
<input type="hidden" name="next" value="{{ request.path }}"/>
{{ xsrf_form_html() }}
</td>
+ <tr>
+ <td><a href="/stats" target="_blank">site statistics</a></td>
+ </tr>
</tr>
</table>
</form>
87 demos/websocket/chat/templates/stats.html
View
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="http://yandex.st/jquery/1.7.1/jquery.min.js"></script>
+ <script src="http://yandex.st/jquery/flot/0.7/jquery.flot.min.js"></script>
+ <script>
+ $(document).ready(function() {
+ updater.start();
+ });
+
+ $(document).unload(function() {
+ });
+
+ updater = {
+ socket: null,
+
+ plot: null,
+ visits: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+ chatters: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+ points: 10,
+
+ options: {
+ legend: { show: true }
+ },
+
+ start: function() {
+ if ("WebSocket" in window) {
+ socket = new WebSocket("ws://localhost:8888/statssocket");
+ } else {
+ socket = new MozWebSocket("ws://localhost:8888/statssocket");
+ }
+ socket.onmessage = function(event) {
+ data = JSON.parse(event.data);
+ updater.update_plot(data);
+ }
+ updater.plot = $.plot($('#placeholder'),
+ [ updater.visits, updater.chatters ],
+ updater.options);
+ },
+
+ append_data: function(visits, chatters) {
+ if (updater.visits.length > updater.points) {
+ updater.visits = updater.visits.slice(1);
+ updater.chatters = updater.chatters.slice(1);
+ }
+ updater.visits.push(visits);
+ updater.chatters.push(chatters);
+ },
+
+ prepare_data: function(arr) {
+ var len = arr.length;
+ var res = [];
+ for (var i=0; i < len; i++) {
+ res.push([i, arr[i]]);
+ }
+ return res;
+ },
+
+ update_plot: function(data) {
+ updater.append_data(data.visits, data.chatters);
+ var visits = updater.prepare_data(updater.visits);
+ var chatters = updater.prepare_data(updater.chatters);
+ updater.plot = $.plot($('#placeholder'),
+ [ {label: 'Global visits', data: visits},
+ {label: 'Active users', data: chatters}
+ ], updater.options);
+ },
+
+ plot: function(visits, chatters) {
+ },
+
+ stop: function() {
+ updater.socket.close();
+ updater.socket = null;
+ }
+ };
+ </script>
+<title> Site statistics </title>
+</head>
+<body>
+ <div>
+ <div id="placeholder" style="width:920px;height:400px;float:left;"></div>
+ <div id="table"></div>
+ </div>
+</body>
+</html>
+
Please sign in to comment.
Something went wrong with that request. Please try again.