Metrics Dashboard using STOMP WebSocket

http://cloudsole-metrics.herokuapp.com
https://github.com/thysmichels/cloudsole-metrics

1. Basic WebAppInitializer
2. WebConfiguration with EnableScheduling
3. WebSocketConfiguration with EnableWebSocketMessageBroker
4. Random Number Generator with ApplicationListener
5. Sock.js Javascript

1. Basic WebAppInitializer

package  com.thysmichels.websockets.configuration;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(final ServletContext context) throws ServletException {

        final AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();

        root.scan("com.thysmichels.websockets");

        context.addListener(new ContextLoaderListener(root));

        final ServletRegistration.Dynamic appServlet = context.addServlet("appServlet",new DispatcherServlet(new GenericWebApplicationContext()));
        appServlet.setAsyncSupported(true);
        appServlet.setLoadOnStartup(1);
        appServlet.addMapping("/*");
    }
}

2. WebConfiguration with EnableScheduling

package  com.thysmichels.websockets.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@EnableScheduling
public class WebConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(
        final DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

3. WebSocketConfiguration with EnableWebSocketMessageBroker

package  com.thysmichels.websockets.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
    }

    @Override
    public void registerStompEndpoints(final StompEndpointRegistry registry) {
        registry.addEndpoint("/metrics").withSockJS();
    }

    @Override
    public void configureClientInboundChannel(final ChannelRegistration registration) {}

    @Override
    public void configureClientOutboundChannel(final ChannelRegistration registration) { }

}

4. Random Number Generator with ApplicationListener

package  com.thysmichels.websockets.utils;

import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.core.MessageSendingOperations;
import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class RandomDataGenerator implements ApplicationListener<BrokerAvailabilityEvent> {

    private final MessageSendingOperations<String> messagingTemplate;

    @Autowired
    public RandomDataGenerator(final MessageSendingOperations<String> messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    @Override
    public void onApplicationEvent(final BrokerAvailabilityEvent event) {
    }

    @Scheduled(fixedDelay = 1000)
    public void sendDataUpdates() {
        this.messagingTemplate.convertAndSend("/data", new Random().nextInt(100));
    }
}

5. Sock.js Javascript

/**
 * Created by tmichels on 8/19/14.
 */

//var stompClient = null;
var updateOpts = {'minVal':'0','maxVal':'100','newVal':'1'};

var randomData;
var socket = new SockJS('/metrics');
var client = Stomp.over(socket);

client.connect('admin', 'password', function(frame) {

    client.subscribe("/data", function(message) {
        var point = [ (new Date()).getTime(), parseInt(message.body) ];
        gaugeUpdate('cf-gauge-1', {'minVal':'0','maxVal':'100','newVal':parseInt(message.body)})
        $('#spark-1').each(function(){

            customSparkOptions = {};
            customSparkOptions.minSpotColor = true;
            var sparkOptions = cf_defaultSparkOpts;
            var sparkOptions = $.extend({}, cf_defaultSparkOpts, customSparkOptions);

            data.push(parseInt(message.body));
            createSparkline($(this), data, sparkOptions);
        });

        $('#metric-1 .metric').html(message.body);
        $('#metric-1 .large').html(message.body);
        $('#metric-2 .metric').html(message.body);
        $('#metric-2 .large').html(message.body);
        var element = $(this).data('update');
        cf_rSVPs[$('#svp-1').attr('id')].chart.update(parseInt(message.body));
        $('#svp-1 .chart').data('percent', parseInt(message.body));
        $('#svp-1 .metric').html(message.body);
       // $('#cf-svmc-sparkline .metric').html(message.body);

        $('#cf-rag-1').each(function(){
            // Dummy data for RAG
            ragData = [60,30,parseInt(message.body)];
            ragLabels = ['Success','Bounce','Abandoned'];
            ragOpts = {postfix:'%'}

            cf_rRags[$(this).prop('id')] = new RagChart($(this).prop('id'), ragData, ragLabels, ragOpts);
        });

        $('#cf-pie-1').each(function(){

            var pdata = [
                {
                    value : parseInt(message.body),
                    color : pieSegColors[3],
                    label: 'Success'
                },
                {
                    value : parseInt(message.body)+50,
                    color : pieSegColors[2],
                    label: 'Bounce'
                },
                {
                    value: parseInt(message.body)+100,
                    color: pieSegColors[1],
                    label: 'Abandoned'
                }
            ]

            var $container = $(this);
            var pId = $container.prop('id');

            // Store chart information
            cf_rPs[pId] = {};
            cf_rPs[pId].data = pdata;


             // Set options per chart
             customOptions = {};
             customOptions.animation = false;
             cf_rPs[pId].options = customOptions;

            // Create chart
            createPieChart($container);
        });

        $('#cf-funnel-1').each(function(){
            funData = [parseInt(message.body)+3000,parseInt(message.body)+1500,parseInt(message.body)+500,parseInt(message.body)+250,parseInt(message.body)];
            funLabels = ['Visits','Cart','Checkout','Purchase','Refund'];
            funOptions = {barOpacity:true, layout:'left'};

            cf_rFunnels[$(this).prop('id')] = new FunnelChart($(this).prop('id'), funData, funLabels, funOptions);
        });

    });
});


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s