/******************************************************************************* * Copyright 2015 IBM Corp. * * 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. *******************************************************************************/ 'use strict'; var Probe = require('../lib/probe.js'); var aspect = require('../lib/aspect.js'); var request = require('../lib/request.js'); var util = require('util'); var am = require('..'); function SocketioProbe() { Probe.call(this, 'socket.io'); } util.inherits(SocketioProbe, Probe); SocketioProbe.prototype.attach = function(name, target) { var that = this; if (name != 'socket.io') return target; /* * Don't set __ddProbeAttached__ = true as we need to probe and return * the constructor each time */ /* * Patch the constructor so that we can patch io.sockets.emit() calls * to broadcast to clients. This also picks up calls to io.emit() as * they map down to io.socket.emit() */ var newtarget = aspect.afterConstructor(target, {}, function(target, methodName, methodArgs, context, server) { var broadcast = 'broadcast'; aspect.around( server.sockets, 'emit', function(target, methodName, methodArgs, context) { that.metricsProbeStart(context, broadcast, methodArgs); that.requestProbeStart(context, broadcast, methodArgs); }, function(target, methodName, methodArgs, context, rc) { that.metricsProbeEnd(context, broadcast, methodArgs); that.requestProbeEnd(context, broadcast, methodArgs); } ); return server; }); /* * Remap the listen API to point to new constructor */ newtarget.listen = newtarget; /* * We patch the constructor every time, but only want to patch prototype * functions once otherwise we'll generate multiple events */ if (!target.__prototypeProbeAttached__) { target.__prototypeProbeAttached__ = true; aspect.before(target.prototype, ['on', 'addListener'], function(target, methodName, methodArgs, context) { if (methodArgs[0] !== 'connection') return; if (aspect.findCallbackArg(methodArgs) != undefined) { aspect.aroundCallback(methodArgs, context, function(target, methodArgs, context) { var socket = methodArgs[0]; /* * Patch Socket#emit() calls */ aspect.around( socket, 'emit', function(target, methodName, methodArgs, context) { that.metricsProbeStart(context, methodName, methodArgs); that.requestProbeStart(context, methodName, methodArgs); }, function(target, methodName, methodArgs, context, rc) { that.metricsProbeEnd(context, methodName, methodArgs); that.requestProbeEnd(context, methodName, methodArgs); return rc; } ); /* * Patch socket.on incoming events */ var receive = 'receive'; aspect.before(socket, ['on', 'addListener'], function(target, methodName, methodArgs, context) { aspect.aroundCallback( methodArgs, context, function(target, callbackArgs, context) { that.metricsProbeStart(context, receive, methodArgs); that.requestProbeStart(context, receive, methodArgs); }, function(target, callbackArgs, context, rc) { that.metricsProbeEnd(context, receive, methodArgs); that.requestProbeEnd(context, receive, methodArgs); return rc; } ); }); }); } }); } return newtarget; }; /* * Lightweight metrics probe for Socket.io websocket connections * * These provide: * time: time event started * method: the type of socket.io action * event: the event broadcast/emitted/received * duration: the time for the action to complete */ SocketioProbe.prototype.metricsEnd = function(context, methodName, methodArgs) { if (context && context.timer) { context.timer.stop(); am.emit('socketio', { time: context.timer.startTimeMillis, method: methodName, event: methodArgs[0], duration: context.timer.timeDelta, }); } }; /* * Heavyweight request probes for Socket.io websocket connections */ SocketioProbe.prototype.requestStart = function(context, methodName, methodArgs) { /* * method names are "broadcast", "receive" and "emit" */ if (methodName !== 'receive') { context.req = request.startRequest('socketio', methodName, false, context.timer); } else { context.req = request.startRequest('socketio', methodName, true, context.timer); } }; SocketioProbe.prototype.requestEnd = function(context, methodName, methodArgs) { if (context && context.req) context.req.stop({ method: methodName, event: methodArgs[0] }); }; module.exports = SocketioProbe;