Server Push and the Dial Chart

From Documentation
DocumentationSmall Talks2011MarchServer Push and the Dial Chart
Server Push and the Dial Chart

Author
Sândalo Bessa, Engineer, Sansys Company
Date
March 15 2011
Version
ZK 5.0


Introduction

In this Small Talk we will discuss two innovative tools for dealing with web development. The first is the concept of "Server Push". And the second is the use of the graph, "Dial Chart". "Server Push" opens a new paradigm in how we build web systems, increasing the range of options for web solutions. However, few frameworks provide such an intuitive and easy way to use that technology. Moreover the use of graphics has become an invaluable tool for creating rich interfaces and working with "Server Push" can make the systems much more interesting. In this article we will show how easy it is to integrate these two features using ZK.

The example

To demonstrate the use of these new features, I built a small application that will measure the percentage of memory and cpu usage of the application server. Application.jpg

The index.zul file

The file below is very simple, and demonstrates how easy it is to integrate the view layer and control layer. Most of the lines is intended to generation layout and only four are actually needed to integrate server-push and dial chart.

<window width="100%" id="d">
		<grid pagingPosition="top" width="100%" align="center" style="horizontal-align:center">
			<rows height="100%">
				<row  align="center" valign="top" style="horizontal-align:center" spans="2" zclass="x">
					<label id="label" value="Monitoring" style="horizontal-align:center;font-size:30px"></label>
				</row>
				<row  align="center" valign="top" style="horizontal-align:center" zclass="x">
					<chart id="dial" title="Dial Plot" width="300px" height="300px" type="dial" threeD="false" fgAlpha="128" />
					<chart id="dial2" title="Dial Plot" width="300px" height="300px" type="dial" style="backgrooud-color:blue" threeD="false" fgAlpha="128" />
				</row>
				<row  align="center" valign="top" style="horizontal-align:center" spans="2" zclass="x">
					<button onClick="br.minasgerais.demos.zk.DialWindow.startMonitoring(dial,dial2)" label="START" width="200px" height="40px"></button>
				</row>
				<row  align="center" valign="top" style="horizontal-align:center" spans="2" zclass="x">
					<button onClick="br.minasgerais.demos.zk.DialWindow.stoptMonitoring()" label="STOP" width="200px" height="40px"></button>
				</row>
			</rows>
		</grid>
</window>

Only these lines are necessary,

					<chart id="dial" title="Dial Plot" width="300px" height="300px" type="dial" threeD="false" fgAlpha="128" />
					<chart id="dial2" title="Dial Plot" width="300px" height="300px" type="dial" style="backgrooud-color:blue" threeD="false" fgAlpha="128" />
					<button onClick="br.minasgerais.demos.zk.DialWindow.startMonitoring(dial,dial2)" label="START" width="200px" height="40px"></button>
					<button onClick="br.minasgerais.demos.zk.DialWindow.stoptMonitoring()" label="STOP" width="200px" height="40px"></button>

On server-side

To better organize this talk, I divided the processing on the server side into three classes. However, only one deserves special attention. The other two are just to illustrate the example.

Starting the service

The two methods of the class below are triggered by buttons placed on the page index.zul. The main function of this method is to enable and disable the conversation between the server and client page.

package br.minasgerais.demos.zk;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zul.Chart;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;

public class DialWindow extends Window {
	public static void startMonitoring(Chart chartMemo,Chart chartCPU) throws InterruptedException {
		final Desktop desktop = Executions.getCurrent().getDesktop();
		if (desktop.isServerPushEnabled()) {
			Messagebox.show("Already started");
		} else {
			desktop.enableServerPush(true);
			
			DialModel dialmodel = new DialModel();
			DialModelScale scale = dialmodel.newScale(0.0, 100.0, -120.0, -300.0, 10.0, 10);// scale's configuration data
			scale.setText("Memory Usage(%)");
			scale.newRange(80, 100, "#FF0000", 0.83, 0.89);
			scale.newRange(60, 80, "#FFC426", 0.83, 0.89);
			chartMemo.setModel(dialmodel);
			
			
			DialModel dialmodelCPU = new DialModel();
			DialModelScale scaleCPU = dialmodelCPU.newScale(0.0, 100.0, -120.0, -300.0, 10.0, 10);// scale's configuration data
			scaleCPU.setText("CPU Usage(%)");
			scaleCPU.newRange(80, 100, "#FF0000", 0.83, 0.89);
			scaleCPU.newRange(60, 80, "#FFC426", 0.83, 0.89);
			chartCPU.setModel(dialmodelCPU);
			new WorkingThread(chartMemo, chartCPU).start();
		}
	}

	public static void stoptMonitoring() throws InterruptedException {
		final Desktop desktop = Executions.getCurrent().getDesktop();
		if (!desktop.isServerPushEnabled()) {
			Messagebox.show("Already stoped");
		} else {
			desktop.enableServerPush(false);
		}
	}
}

Working

The next class(WorkingThread) will use a thread to do the job of monitoring. Every second thread the thread will wake up and do the monitoring. Note that the start method takes two charts, which must be updated every second with the monitoring information.

package br.minasgerais.demos.zk;
import org.zkoss.lang.Threads;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.DesktopUnavailableException;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zul.Chart;

public class WorkingThread extends Thread {
	private final Desktop _desktop;
	private final Chart _dialMemo;
	private final Chart _dialCPU;

	public WorkingThread(Chart dialMemo, Chart dialCPU) {
		_desktop = dialMemo.getDesktop();
		_dialMemo = dialMemo;
		_dialCPU = dialCPU;
	}

	public void run() {
		try {
			while (true) {
				if (_dialMemo.getDesktop() == null || !_desktop.isServerPushEnabled()) {
					_desktop.enableServerPush(false);
					return;
				}
				Executions.activate(_desktop);
				try {
					Monitor.refreshDialMemo(_dialMemo);
					Monitor.refreshDialCPU(_dialCPU);
				} finally {
					Executions.deactivate(_desktop);
				}
				Threads.sleep(1000);
			}
		} catch (DesktopUnavailableException ex) {
			System.out.println("The server push thread interrupted");
		} catch (InterruptedException ex) {
			System.out.println("The server push thread interrupted");
		}
	}
}


The run method starts the process and controls the scheduling of monitoring. Every second, another class is called to update the processing.

Capturing data

The Monitor class is used to retrieve data related to operating system features such as: CPU usage and memory usage. However, this class is only for the purpose of this small talk and does not have the required accuracy for a real application.

package br.minasgerais.demos.zk;
import java.io.InputStreamReader;
import java.io.LineNumberReader;

import org.zkoss.zul.Chart;
import org.zkoss.zul.DialModel;
import org.zkoss.zul.DialModelScale;

import sun.management.ManagementFactory;

import com.sun.management.OperatingSystemMXBean;


public class Monitor {
	private static final int CPUTIME = 1000;
	private static final int PERCENT = 100;
	private static final int FAULTLENGTH = 10;

	public static void refreshDialMemo(Chart chart) {
		int val = getMemoryUsage();
		DialModel dialmodel = (DialModel) chart.getModel();
		DialModelScale scale = dialmodel.getScale(0);// scale's configuration data
		if (val > 100) {
			val = 100;
		} else if (val < 0) {
			val = 0;
		}
		scale.setValue(val);
		if (val > 80) {
			scale.setNeedleColor(scale.getRange(0).getRangeColor());
		} else if (val > 60) {
			scale.setNeedleColor(scale.getRange(1).getRangeColor());
		} else {
			scale.setNeedleColor(dialmodel.getFrameFgColor());
		}
		chart.setModel(dialmodel);
	}


	public static void refreshDialCPU(Chart chart) {
		int val = getCpuRatioForWindows();

		DialModel dialmodel = (DialModel) chart.getModel();
		DialModelScale scale = dialmodel.getScale(0);// scale's configuration data

		if (val > 100) {
			val = 100;
		} else if (val < 0) {
			val = 0;
		}
		scale.setValue(val);
		if (val > 80) {
			scale.setNeedleColor(scale.getRange(0).getRangeColor());
		} else if (val > 60) {
			scale.setNeedleColor(scale.getRange(1).getRangeColor());
		} else {
			scale.setNeedleColor(dialmodel.getFrameFgColor());
		}
		chart.setModel(dialmodel);
	}	
	
	private static int getMemoryUsage() {
		OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
		long totalMemory = osmxb.getTotalPhysicalMemorySize();
		long freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize();
		Double memoryUsage = (Double) (1 - freePhysicalMemorySize * 1.0 / totalMemory) * 100;
		int val = memoryUsage.intValue();
		return val;
	}

	
	public static int getCpuRatioForWindows() {
		try {
			String procCmd = System.getenv("windir") + "\\system32\\wbem\\wmic.exe process get Caption, KernelModeTime, ReadOperationCount, ThreadCount, UserModeTime, WriteOperationCount";
			// Get process information
			long[] c0 = readCpu(Runtime.getRuntime().exec(procCmd));
			Thread.sleep(CPUTIME);
			long[] c1 = readCpu(Runtime.getRuntime().exec(procCmd));
			if (c0 != null && c1 != null) {
				long idletime = c1[0] - c0[0];
				long busytime = c1[1] - c0[1];
				return Double.valueOf(PERCENT * (busytime) * 1.0 / (busytime + idletime)).intValue();
			} else {
				return 0;
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			return 0;
		}
	}

	// Read the cpu information
	private static long[] readCpu(final Process proc) {
		long[] retn = new long[2];
		try {
			proc.getOutputStream().close();
			InputStreamReader ir = new InputStreamReader(proc.getInputStream());
			LineNumberReader input = new LineNumberReader(ir);
			String line = input.readLine();
			if (line == null || line.length() < FAULTLENGTH || line.equals("")) {
				return null;
			}
			String[] labels = line.replaceAll("\\s\\s+",",").split(",");
			long idletime = 0;
			long kneltime = 0;
			long usertime = 0;
			String[] fields = null;
			while ((line = input.readLine()) != null) {
				if (line.length() == 0) {
					continue;
				}
				fields = line.replaceAll("\\s\\s+",",").split(",");
				String caption = fields[0];
				if (caption.indexOf("WMIC.exe") >= 0) {
					continue;
				}
				//KernelModeTime
				String s1 = fields[1];
				//UserModeTime
				String s2 = fields[4];
				if (caption.equals("System Idle Process") || caption.equals("System")){
					if (s1.length() > 0)
						idletime += Long.valueOf(s1).longValue();
					if (s2.length() > 0)
						idletime += Long.valueOf(s2).longValue();
					continue;
				}
				if (s1.length() > 0)
					kneltime += Long.valueOf(s1).longValue();
				if (s2.length() > 0)
					usertime += Long.valueOf(s2).longValue();
			}
			retn[0] = idletime;
			retn[1] = kneltime + usertime;
			return retn;
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			try {
				proc.getInputStream().close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}

Conclusion

In this small talk we demonstrated how easy it is to integrate "server push" and the "dial chart" using ZK. I hope this work will serve as a base for people who would like to explore both these tools.


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.