Commit d4dac92e d4dac92e8288a20590aa79dc310879bfc9f7e014 by Christian Gerdes

Ändrat mappningen mellan servlets och uri så att de ligger under en

context root OraMonREST/ istället. Döpt om den till getData samt skapat
en som heter getMetrics. Lagt in thread safety på getData och testat med
10 paralella anrop utan att det nu går fel. getMetrics returnerar samma
set av data som javamon vu:n fast i json format. Ändrat OraMon.jsp så
den visar vettig status om monitorerna, som antal anrop, svarstid, ålder
på datat. getData tar nu även en age parameter som kontrollerar
cachning. Därmed kan man alltid anropa denna innan getMetrics utan att
det blir onödiga db anrop. Testat mot Oracle XE 11.2.
1 parent cfa10ffe
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry combineaccessrules="false" kind="src" path="/LILOM Library"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="owner.project.facets" value="java"/>
......
......@@ -37,13 +37,30 @@ if(request.getParameter("action") != null) {
<h2>Status</h2>
<p>Number of monitors: <%= monList.size() %></p>
<% for (OraMon mon : Registry.getList()) { %>
<div style="background-color: LightSteelBlue; padding: 10px;">
<table>
<tr><td>Connection String</td><td><%= mon.getConString() %></td></tr>
<tr>
<th>Connection String</th>
<th>Data Calls</th>
<th>Success</th>
<th>Failed</th>
<th>Age</th>
<th>RT</th>
</tr>
<% for (OraMon mon : Registry.getList()) { %>
<tr>
<td><%= mon.getConString() %></td>
<td><%= mon.getDataCalled() %></td>
<td><%= mon.getDataSucceeded() %></td>
<td><%= mon.getDataFailed() %></td>
<td><%= mon.getAgeTs() %>s</td>
<td><%= mon.getLastRTms() %>ms</td>
</tr>
<% } %>
</table>
</div>
<% } %>
<h2>Administration</h2>
......
/readme.txt
/OraMonREST.class
/OraMonRESTgetData.class
/OraMonRESTgetMetrics.class
......
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
......@@ -9,19 +8,20 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import se.lil.om.OraMon;
import se.lil.om.Registry;
/**
* Servlet implementation class OraMonREST
*/
@WebServlet(description = "REST API for OraMon", urlPatterns = { "/OraMonREST" })
public class OraMonREST extends HttpServlet {
@WebServlet(description = "REST API for OraMon", urlPatterns = { "/OraMonREST/getData" })
public class OraMonRESTgetData extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public OraMonREST() {
public OraMonRESTgetData() {
super();
// TODO Auto-generated constructor stub
}
......@@ -31,27 +31,62 @@ public class OraMonREST extends HttpServlet {
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json");
StringBuffer sb = new StringBuffer();
if(request.getParameterMap().containsKey("connectionString")) {
// We have a connection string, find the monitor or create it and call getData() on the monitor
// Check that we have a valid connection string
String[] conStrParamArray = request.getParameter("connectionString").split(":");
if(conStrParamArray.length != 5) {
// Error, we need 3 parts in the con string
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "connectionString needs to be in format host:port:sid:username:password");
return;
try {
StringBuffer sb = new StringBuffer();
int age = 0;
if(request.getParameterMap().containsKey("age")) {
try {
age = Integer.parseInt(request.getParameter("age"));
} catch (Exception e) {
response.setStatus(400);
response.getWriter().println("{\"error\":true,\"msg\":\"The specified age parameter is not a valid integer number\"}");
return;
}
}
String conStr = "jdbc:oracle:thin:@" + conStrParamArray[0] + ":" + conStrParamArray[1] + ":" + conStrParamArray[2];
String userStr = conStrParamArray[3];
String passStr = conStrParamArray[4];
} else {
// No input, just return the list of monitors
sb.append("{\"count\":" + Registry.getList().size() + "}");
if(request.getParameterMap().containsKey("connectionString")) {
// We have a connection string, find the monitor or create it and call getData() on the monitor
// Check that we have a valid connection string
String[] conStrParamArray = request.getParameter("connectionString").split(":");
if(conStrParamArray.length != 5) {
// Error, we need 5 parts in the connection string
response.setStatus(400);
response.getWriter().println("{\"error\":true,\"msg\":\"connectionString needs to be in format host:port:sid:username:password\"}");
return;
}
String conStr = "jdbc:oracle:thin:@" + conStrParamArray[0] + ":" + conStrParamArray[1] + ":" + conStrParamArray[2];
String usrStr = conStrParamArray[3];
String pwdStr = conStrParamArray[4];
// Try to find the monitor in our list
OraMon monitor = Registry.findOrCreate(conStr, usrStr, pwdStr);
// Call getData()
boolean didUpdate = monitor.getData(age);
String dbName = monitor.getDBName();
if(didUpdate) {
sb.append("{\"error\":false,\"msg\":\"Sucessfully collected data on instance '" + dbName + "'\"");
sb.append(",\"ms\":"+monitor.getLastRTms()+"}");
} else {
response.setStatus(HttpServletResponse.SC_ACCEPTED);
sb.append("{\"error\":false");
sb.append(",\"msg\":\"Data does not need to be updated on instance '" + dbName + "' either because another thread did concurrently update the monitor and we just waited for it to complete, or because the age (if) specified was higher than the monitors data age.\"");
sb.append(",\"age\":"+monitor.getAgeTs()+"}");
}
} else {
// No input, just return the list of monitors
sb.append("{\"count\":" + Registry.getList().size() + "}");
}
response.getWriter().println(sb.toString());
} catch (Throwable e) {
response.setStatus(500);
response.getWriter().println("{\"error\":true,\"msg\":\""+e.toString().replace("\n"," ").trim()+"\"}");
e.printStackTrace();
}
PrintWriter out = response.getWriter();
out.println(sb.toString());
}
/**
......
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import se.lil.om.OraMon;
import se.lil.om.Registry;
/**
* Servlet implementation class OraMonRESTgetMetrics
*/
@WebServlet(description = "Gets a default set of metrics from an existing monitor", urlPatterns = { "/OraMonREST/getMetrics" })
public class OraMonRESTgetMetrics extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public OraMonRESTgetMetrics() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json");
try {
StringBuffer sb = new StringBuffer();
ArrayList<OraMon> list = Registry.getList();
if(request.getParameterMap().containsKey("connectionString")) {
// We have a connection string, find the monitor and report only on that monitor
// Check that we have a valid connection string
String[] conStrParamArray = request.getParameter("connectionString").split(":");
if(conStrParamArray.length < 3) {
// Error, we need at least 3 parts in the connection string
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("{\"error\":true,\"msg\":\"connectionString needs to be in format host:port:sid\"}");
return;
}
String conStr = "jdbc:oracle:thin:@" + conStrParamArray[0] + ":" + conStrParamArray[1] + ":" + conStrParamArray[2];
// Try to find the monitor in our list
OraMon monitor = null;
for (OraMon item : Registry.getList()) {
if(item.getConString().equals(conStr)) {
monitor = item;
}
}
// If not found return error
if(monitor == null) {
// Not found, create it
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().println("{\"error\":true,\"msg\":\"Can not find a monitor with connection string "+conStr+"\"}");
return;
}
// Set this monitor to the only one on the list
list = new ArrayList<OraMon>();
list.add(monitor);
}
// Loop over all monitors and output a Json array with the default set of metrics
sb.append("{\"error\":false,\"nvarray\":");
String aStr = "";
sb.append("[");
for (OraMon item : list) {
if (list.size() > 1) aStr = " " + item.getDBName();
sb.append("{\"name\":\"Cpus (#)" + aStr + "\",\"value\":" + item.getNumberOfCPUs() + "}");
sb.append(",{\"name\":\"Cpu Time (ms/s)" + aStr + "\",\"value\":" + item.getCPUTimePerSecond() + "}");
sb.append(",{\"name\":\"Cpu Usage (%)" + aStr + "\",\"value\":" + item.getCPUPercent() + "}");
sb.append(",{\"name\":\"Logical Reads (#/s)" + aStr + "\",\"value\":" + item.getLogicalReadsPerSecond() + "}");
sb.append(",{\"name\":\"Consistent Gets (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("consistent gets") + "}");
sb.append(",{\"name\":\"DB Block Gets (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("db block gets") + "}");
sb.append(",{\"name\":\"Cache Hit Ratio (%)" + aStr + "\",\"value\":" + item.getCacheHitRatioPercent() + "}");
sb.append(",{\"name\":\"Buffer Cache Hit Ratio (%)" + aStr + "\",\"value\":" + item.getBufferCacheHitRatioPercent() + "}");
sb.append(",{\"name\":\"DB Block Changes (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("db block changes") + "}");
sb.append(",{\"name\":\"Redo Size (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("redo size") + "}");
sb.append(",{\"name\":\"Physical Reads (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("physical reads") + "}");
sb.append(",{\"name\":\"Physical Writes (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("physical writes") + "}");
sb.append(",{\"name\":\"Redo Writes (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("redo writes") + "}");
sb.append(",{\"name\":\"Non-idle Wait Time (ms/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("non-idle wait time") + "}");
sb.append(",{\"name\":\"File I/O Wait Time (ms/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("file io wait time") + "}");
sb.append(",{\"name\":\"Executes (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("execute count") + "}");
sb.append(",{\"name\":\"User Calls (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("user calls") + "}");
sb.append(",{\"name\":\"User Commits (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("user commits") + "}");
sb.append(",{\"name\":\"User Rollbacks (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("user rollbacks") + "}");
sb.append(",{\"name\":\"Parse Count Total (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("parse count (total)") + "}");
sb.append(",{\"name\":\"Parse Count Hard (#/s)" + aStr + "\",\"value\":" + item.getPerSecondValue("parse count (hard)") + "}");
}
sb.append("]}");
response.getWriter().println(sb.toString());
} catch (Throwable e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().println("{\"error\":true,\"msg\":\""+e.toString().replace("\n"," ").trim()+"\"}");
e.printStackTrace();
}
}
}
/Collector.class
/LongDelta.class
/OraMon.class
/Registry.class
/TestRunner.class
package se.lil.om;
import java.sql.*;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class OraMon {
Connection conn = null;
......@@ -11,6 +14,21 @@ public class OraMon {
Collector col = new Collector();
int getDataCalls = 0;
int getDataSucess = 0;
public int getDataCalled() {
return getDataCalls;
}
public int getDataSucceeded() {
return getDataSucess;
}
public int getDataFailed() {
return getDataCalls - getDataSucess;
}
public String getConString() {
return this.conString;
}
......@@ -74,30 +92,86 @@ public class OraMon {
return getPerSecondValue("consistent gets") + getPerSecondValue("db block gets");
}
public void getData() throws Throwable {
Statement stmt = conn.createStatement();
ResultSet rset = null;
//Get the database name once
if(this.dbName == null) {
rset = stmt.executeQuery("select value from V$SYSTEM_PARAMETER where name = 'db_name'");
long lastRTns = 0;
public long getLastRTms() {
return lastRTns / (1000 * 1000);
}
long lastFetchTSns = 0;
public boolean getData(long ageTs) throws Throwable {
if ((System.nanoTime() - lastFetchTSns) > (ageTs * 1000 * 1000 * 1000)) return getData();
else return false;
}
public long getAgeTs() {
return (System.nanoTime() - lastFetchTSns)/(1000*1000*1000);
}
Lock lock = new ReentrantLock();
public boolean getData() throws Throwable {
// Start thread safe
// Try to get a lock
boolean haveLock = lock.tryLock();
if( haveLock == false) {
// We could not get the lock, someone else is updating. Wait for it to complete by waiting for a lock, then unlock and return.
try {
lock.lock();
} finally {
lock.unlock();
}
return false;
}
// We do have the lock. Do the rest in a try catch everything and if we catch anything, re throw the catch but always release the lock.
try {
long startTSns = System.nanoTime();
getDataCalls++;
if(conn == null) open();
if(conn.isClosed()) open();
boolean stale = true;
try {
if (conn.isValid(20)) {
stale = false;
}
} catch (SQLException e) {
stale = true;
}
if(stale) {
open();
}
Statement stmt = conn.createStatement();
ResultSet rset = null;
//Get the database name once
if(this.dbName == null) {
rset = stmt.executeQuery("select value from V$SYSTEM_PARAMETER where name = 'db_name'");
rset.next();
this.dbName = rset.getString(1);
rset.close();
}
//Get values for CPU calculation
rset = stmt.executeQuery("select value from V$OSSTAT where STAT_NAME = 'NUM_CPUS'");
rset.next();
this.dbName = rset.getString(1);
this.numCpus = rset.getInt(1);
rset.close();
cpuTime.update(stmt.executeQuery("select systimestamp, value from V$SYS_TIME_MODEL where stat_name='DB CPU'"), true);
// Get the entire V_$SYSSTAT table from DB
col.update(stmt.executeQuery("select systimestamp, name, value from V$SYSSTAT"));
stmt.close();
getDataSucess++;
lastFetchTSns = System.nanoTime();
lastRTns = lastFetchTSns - startTSns;
return true;
} catch (Throwable e) {
throw (e);
} finally {
lock.unlock();
}
//Get values for CPU calculation
rset = stmt.executeQuery("select value from V$OSSTAT where STAT_NAME = 'NUM_CPUS'");
rset.next();
this.numCpus = rset.getInt(1);
rset.close();
cpuTime.update(stmt.executeQuery("select systimestamp, value from V$SYS_TIME_MODEL where stat_name='DB CPU'"), true);
// Get the entire V_$SYSSTAT table from DB
col.update(stmt.executeQuery("select systimestamp, name, value from V$SYSSTAT"));
stmt.close();
// End thread safe
}
public OraMon() {
......@@ -112,7 +186,12 @@ public class OraMon {
public void open() throws Throwable {
Class.forName ("oracle.jdbc.OracleDriver");
this.conn = DriverManager.getConnection(conString, conUser, conPass);
Properties jdbcProps = new Properties();
jdbcProps.put("user", conUser);
jdbcProps.put("password", conPass);
jdbcProps.put("v$session.program","LIL Oracle Monitor");
this.conn = DriverManager.getConnection(conString, jdbcProps);
}
public void open(String con, String user, String pass) throws Throwable {
......@@ -120,7 +199,11 @@ public class OraMon {
if(con != null) this.conString = con;
if(user != null) this.conUser = user;
if(pass != null) this.conPass = pass;
this.conn = DriverManager.getConnection(conString, conUser, conPass);
Properties jdbcProps = new Properties();
jdbcProps.put("user", conUser);
jdbcProps.put("password", conPass);
jdbcProps.put("v$session.program","LIL Oracle Monitor");
this.conn = DriverManager.getConnection(conString, jdbcProps);
}
public void close() throws Throwable {
......
......@@ -4,11 +4,24 @@ import java.util.ArrayList;
public class Registry {
private static ArrayList<OraMon> oraList = null;
public static boolean test = true;
public static ArrayList<OraMon> getList() {
public static synchronized ArrayList<OraMon> getList() {
if (oraList == null) {
oraList = new ArrayList<OraMon>();
}
return oraList;
}
public static synchronized OraMon findOrCreate(String conStr, String usrStr, String pwdStr) {
for (OraMon item : getList()) {
if(item.getConString().equals(conStr)) {
return item;
}
}
// Not found, create it
OraMon monitor = new OraMon(conStr, usrStr, pwdStr);
oraList.add(monitor);
return monitor;
}
}
......