OraMon.java 11 KB
package se.lil.om;

import java.sql.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class OraMon {
	Connection conn = null;
	String conString = "jdbc:oracle:thin:@//hostname:1521/SID";
	String conUser = "system";
	String conPass = "passw0rd";
	String dbName = null;
	String serviceName = null;
	
	Boolean isPDB = false;
	Boolean isCDB = false;
	Boolean hasContainers = null;
	OraMon containerDB = null;
	
	private Hashtable<String, OraMon> myPDBs = null;
	
	public static final int SYSSTAT = 1;
	public static final int OSSTAT = 2;
	
	Collector colSysStat = new Collector();
	Collector colOsStat = 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;
	}
	
	public String getServiceName() {
		if(this.serviceName != null) return this.serviceName;
		if(this.conString != null) {
			if(this.conString.contains("SERVICE_NAME=")) {
				//TNS format
				Pattern pat = Pattern.compile("SERVICE_NAME=(\\w+)");
				Matcher m = pat.matcher(this.conString);
				if(m.find()) {
					this.serviceName = m.group(1);
				} else {
					this.serviceName = null;
				}
			} else {
				int last = this.conString.lastIndexOf('/');
				if(last >=0)
					this.serviceName = this.conString.substring(last +1);
				else {
					last = this.conString.lastIndexOf(':');
					this.serviceName = this.conString.substring(last +1);
				}
			}
		} else {
			this.serviceName = null;
		}
		return this.serviceName;
	}
	
	//NUM_CPUS
	int numCpus = 0;
	public long getNumberOfCPUs() throws Throwable {
		return numCpus;
	}
	
	//DB CPU
	LongDelta cpuTime = new LongDelta(true);
	LongDelta niwTime = new LongDelta(true);
	public double getCPUPercent(boolean excludeNonIdleWaitTime) throws Throwable {
		double perSec = cpuTime.getPerSecondValue();
		if(excludeNonIdleWaitTime) {
			double newPerSec = perSec - (niwTime.getPerSecondValue() * 1000);
			if(newPerSec > 0) perSec = newPerSec; else perSec = 0;
		}
		double totSec = numCpus * 1000 * 1000;
		double percent = (perSec / totSec) * 100;
		if(percent > 100D) percent=100D;
		if(percent < 0D) percent=0D;
		return percent;
	}
	public double getCPUPercent() throws Throwable {
		return getCPUPercent(false);
	}
	public double getCPUAvailableSeconds() throws Throwable {
		return cpuTime.getSeconds()*numCpus;
	}
	public long getCPURawValue() throws Throwable {
		return cpuTime.getCurrentValue();
	}
	public long getCPURawDiff() {
		if(cpuTime != null && cpuTime.lastValue != null)
			return cpuTime.curValue - cpuTime.lastValue;
		else
			return 0;
	}
	
	public double getCPUTimePerSecond() throws Throwable {
		return cpuTime.getPerSecondValue();
	}
	
	public double getPerSecondValue(String name) throws Throwable {
		return getPerSecondValue(name, SYSSTAT);
	}
	
	public double getPerSecondValue(String name, int table) throws Throwable {
		switch (table) {
		case OSSTAT:
			return colOsStat.getPerSecValue(name);
		case SYSSTAT:
			return colSysStat.getPerSecValue(name);
		default:
			return colSysStat.getPerSecValue(name);
		}
	}
	
	public String getDBName() {
		// Returns the instance name unless we are a PDB, in that case the service name from the con string is returned instead.
		return this.dbName;
	}
	
	public String getFriendlyName() {
		// Returns a friendly name of the database, either the service name from con string or the database name from the instance
		getServiceName();
		if(this.serviceName != null) return this.serviceName;
		else return this.dbName;
	}
	
	public boolean isPDB() {
		return isPDB;
	}
	
	public double getOsBusyPercent() throws Throwable {
		double idle = getPerSecondValue("IDLE_TIME", OSSTAT);
		double busy = getPerSecondValue("BUSY_TIME", OSSTAT);
		if(idle != 0 && busy != 0)
			return 100*(busy/(busy+idle));
		else {
			if(busy == 0)
				return 0;
			else
				return 100;
		}
	}
	
	// OS LOAD
	DoubleDelta osLoad = new DoubleDelta();
	
	public double getOsLoad() throws Throwable {
		return osLoad.getCurrentValue();
	}
	
	public double getOsLoadPerCPU() throws Throwable {
		double num = this.numCpus;
		double load = getOsLoad();
		return load / num;
	}
	
	public double getBufferCacheHitRatioPercent() throws Throwable {
		double conGetsCache = getPerSecondValue("consistent gets from cache");
		double dbBlocksCache = getPerSecondValue("db block gets from cache");
		double physReadsCache = getPerSecondValue("physical reads cache");
		if (conGetsCache + dbBlocksCache + physReadsCache == 0) {
			return 100;
		}
		if (conGetsCache + dbBlocksCache == 0 && physReadsCache > 0) {
			return 0;
		}
		double buffCacheHitRatio = 1 - (physReadsCache/(conGetsCache + dbBlocksCache));
		if(buffCacheHitRatio > 0)
			return buffCacheHitRatio * 100;
		else
			return 0;
		
	}
	
	public double getCacheHitRatioPercent() throws Throwable {
		double conGets = getPerSecondValue("consistent gets");
		double dbBlocks = getPerSecondValue("db block gets");
		double physReads = getPerSecondValue("physical reads");
		if (conGets + dbBlocks + physReads == 0) {
			return 100;
		}
		if (conGets + dbBlocks == 0 && physReads > 0) {
			return 0;
		}
		double cacheHitRatio = 1 - (physReads/(conGets + dbBlocks));
		if(cacheHitRatio > 0)
			return cacheHitRatio * 100;
		else
			return 0;
	}
	
	public double getLogicalReadsPerSecond() throws Throwable {
		return getPerSecondValue("consistent gets") + getPerSecondValue("db block gets");
	}
	
	long lastRTns = 0;
	public long getLastRTms() {
		return lastRTns / (1000 * 1000);
	}
	
	long lastFetchTSns = 0;
	
	public long getAgeTs() {
		return (System.nanoTime() - lastFetchTSns)/(1000*1000*1000);
	}
	
	public boolean getData() throws Throwable {
		return getData(0);
	}
	
	Lock lock = new ReentrantLock();
	Throwable lastEx = null;
	
 	public boolean getData(long ageTs) throws Throwable {
 		//Check age
 		if ((System.nanoTime() - lastFetchTSns) < (ageTs * 1000 * 1000 * 1000)) return false;

 		// 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 // Currently always gets the CDB name
			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(); 
			}
			
			// Check if we can have containers
			if(hasContainers == null) {
				// We don't know, check if the CONTAINERS view exists in the database
				rset = stmt.executeQuery("select count(*) from SYS.ALL_VIEWS where VIEW_NAME = 'V_$CONTAINERS'");
				if(rset.next()) {
					if(rset.getInt(1) == 1) {
						// We found the containers view. Enable container handling
						hasContainers = true;
						String serviceName = getServiceName();
						if(serviceName != null) {
							if(serviceName.equals(this.dbName)) isCDB = true;
							else isPDB = true;
						} else {
							// Servicename is null, treat as CDB
							isCDB = true;
							isPDB = false;
						}
					}
				}
				if(hasContainers == null) {
					hasContainers = false;
					isCDB = true;
					isPDB = false;
				}
			}
			
			// If we have containers, but we are not the CDB, then we are a PDB. 
			// We should ask the CDB object for our data
			if(isPDB) {
				getDataFromCDB();
			} else {
				// All other cases (CDB or an instance without containers) get the data from the database
				
				//Get the entire V_$OSSTAT table from DB
				colOsStat.update(stmt.executeQuery("select systimestamp, stat_name, value from V$OSSTAT"));
				
				//Set numCpus
				this.numCpus = (int) colOsStat.getCurrentValue("NUM_CPUS");
				
				// Get OS Load
				osLoad.update(stmt.executeQuery("select systimestamp, value from V$OSSTAT where stat_name='LOAD'"));
				
				// SYSSTAT and SYS_TIME_MODEL are specific for PDB and CDB/Instance is the total.
				
				// Get DB CPU use time
				rset = stmt.executeQuery("select systimestamp, s.value as NIWT, t.value as DBCPU from V$SYSSTAT s, V$SYS_TIME_MODEL t where s.name='non-idle wait time' and t.STAT_NAME='DB CPU'");
				rset.next();
				niwTime.update(rset.getLong(2), rset.getTimestamp(1));
				cpuTime.update(rset.getLong(3), rset.getTimestamp(1));
				rset.close();
		
				//Get the entire V_$SYSSTAT table from DB
				colSysStat.update(stmt.executeQuery("select systimestamp, name, value from V$SYSSTAT"));
			}
			
			stmt.close(); 
			getDataSucess++;
			lastFetchTSns = System.nanoTime();
			lastRTns = lastFetchTSns - startTSns;
			return true;
		} catch (Throwable e) {
			Throwable lastCause = e.getCause();
			Throwable thisCause = e;
			while(lastCause != null) { thisCause = lastCause; lastCause = thisCause.getCause(); }
			lastEx = thisCause;
			throw (e);
		} finally {
			lock.unlock();
		}
		// End thread safe
	}
 	
 	public void getDataFromCDB() {
 		//Generate a con string for the CDB using the PDB con string and replace the service name
 		String cdbConString = getConString().replaceAll(getServiceName(), getDBName());
 		System.out.println("CDB seems to be: " + cdbConString);
 	}
 	
	public Throwable getLastEx() {
		return lastEx;
	}
	
	public OraMon() {
		// TODO Auto-generated constructor stub
	}
	
	public OraMon(String con, String user, String pass) {
		this.conString = con;
		this.conUser = user;
		this.conPass = pass;
	}
	
	public void open() throws Throwable {
		Class.forName ("oracle.jdbc.OracleDriver");
		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 {
		Class.forName ("oracle.jdbc.OracleDriver");
		if(con != null) this.conString = con;
		if(user != null) this.conUser = user;
		if(pass != null) this.conPass = pass;
		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 {
		if(conn != null) conn.close();
	}
}