Using Cassandra Metrics for monitoring - java

I have been using Cassandra and its C++ driver to write APIs to insert and fetch data for some time now. However now that I've created my cluster, I want to develop monitoring tools for my cluster.
I want to build an application(preferably in C++ and I don't want to use a 3rd party application), which will store Cluster management specific attributes like memory utilization of each node in the cluster, latency of each operation, space occupied by each table on each node etc. I read about 'Metrics in Cassandra(https://cassandra.apache.org/doc/latest/operating/metrics.html) but I don't know how exactly to use them in building my application as I've not worked on Java before(excuse me for that!).
Can such application be built using C++? If it's a lot of work in C++, then it will be highly beneficial if you can share some Java code where these Cassandra Metrics have been used to monitor a Cassandra Cluster.
OS: RHEL
Cassandra version: 3.11.2

Cassandra 3.x uses the drop wizard api as you alluded to. If you can add the Jolokia jars to your deployment server(s) this will allow you to access the java jmx data using a simple http request. Jolokia exposes all the mbeans from java over a rest api.

It seems, there are no any c++ libs for JMX, but in Java it is pretty easy to get JMX metrics, all of you need is standard jdk. The following code demonstrates how to connect to cassandra node and get 'down' node count.
import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class Main {
public static void main( String[] args ) throws Exception {
String node = args[0];
String port = args[1];
String username = args[2];
String password = args[3];
JMXServiceURL url = new JMXServiceURL( "service:jmx:rmi:///jndi/rmi://" + node + ":" + port + "/jmxrmi" );
String[] credentials = {username, password};
Map<String, String[]> environment = new HashMap<>();
environment.put( JMXConnector.CREDENTIALS, credentials );
JMXConnector jmxConnector = JMXConnectorFactory.connect( url, environment );
MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection();//Get metrics bean
ObjectName oName = new ObjectName( "org.apache.cassandra.net:type=FailureDetector" );//create JMX object name
int downNodes = (int) mbsc.getAttribute( oName, "DownEndpointCount" ); //get number of unavailable nodes
System.out.println("Down node count: " + downNodes);
}
}
More details about jmx you can find in Oracle documentation
To obtain JMX object names and attribute names you can use jconsole tool, which is shipped together with jdk:

Related

Trigger Snapshots on Remote Server from local

I am trying to remotely profile alfresco running on a 64-bit linux server running a 1.8 JVM and Apache Tomcat 7.xx from my testing code but can't figure out how to programatically trigger snapshots.
What I want to do is to connect to the remote server, start profiling, and save a snapshot of that server's performance onto my local machine from my testing code which is written in Java.
I've already installed JProfiler 9.2 onto the linux server and can connect and take snapshots via the JProfiler GUI. The server also requires an SSH connection for security. I'd like to do this from my code, similar to how Controller.saveSnapshot(file) works for local JVMs.
Is this possible?
I know I can set triggers and have the remote profiler save snapshots on the server, but this isn't what I want to.
Additionally, I looked into using the command line controller but was unable to get it to connect to the server even with the correct arguments in the remote VM options.
I also tried to use ConnectionFactor.createRemoteConnection(), but don't see an argument that allows for a password to be input, so it fails.
You can access the JProfiler MBean programmatically. Below is an example on how to do that. I would run such a program on the remote machine and start it via SSH, because JMX connnections are difficult to tunnel through SSH.
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
// Shows how to connect to the the JProfiler MBean programatically
// The profiled process has to be in offline profiling mode and the JMX server
// has to be started by passing -Djprofiler.jmxServerPort=[port] to the profiled JVM.
// This will not work in nowait mode because the MBean is not registered in that case.
public class MBeanProgrammaticAccessExample {
public static void main(String[] args) throws Exception {
if (args.length == 0) {
System.out.println("Specify the port as an argument that was passed to " +
"the profiled JVM with the VM parameter " +
"-Djprofiler.jmxServerPort=[port]");
}
String port = args[0];
// In this case the connection is made to a process on localhost, but it could
// be on a remote system as well. Note that the connection is made via JMX which
// does not work well with firewalls
System.out.println("Connecting to localhost:" + port);
JMXServiceURL jmxUrl = new JMXServiceURL(
"service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi");
JMXConnector connector = JMXConnectorFactory.newJMXConnector(jmxUrl,
Collections.<String, Object>emptyMap());
Map<String, Object> env = new HashMap<>();
// If you have protected the JMX server with a JMX password file by passing
// -Djprofiler.jmxPasswordFile=[file] to the profiled JVM, you can specify
// the password like this:
//env.put(JMXConnector.CREDENTIALS, new String[] {"username", "password"});
connector.connect(env);
MBeanServerConnection connection = connector.getMBeanServerConnection();
ObjectName objectName = new ObjectName(
"com.jprofiler.api.agent.mbean:type=RemoteController");
if (!connection.isRegistered(objectName)) {
throw new RuntimeException("JProfiler MBean not found.");
}
RemoteControllerMBean mbeanProxy = JMX.newMBeanProxy(connection,
objectName, RemoteControllerMBean.class, true);
// You can look up all available operations in the javadoc of
// com.jprofiler.api.agent.mbean.RemoteControllerMBean
System.out.println("Recording CPU data for 5 seconds ....");
mbeanProxy.startCPURecording(true);
// If you do not want a dependency on the JProfiler classes
// you can make the above call like this:
//connection.invoke(objectName, "startCPURecording", new Object[] {true},
// new String[] {Boolean.TYPE.getName()});
Thread.sleep(5000);
System.out.println("Saving snapshot to the working directory " +
"of the profiled JVM ....");
mbeanProxy.saveSnapshot("snapshot.jps");
connector.close();
System.out.println("Success");
}
}

How does jconsole discover the JMX enabled processes on local system?

When I run jconsole it shows me a list of Java processes:
I could then connect to one of these and see its MBeans. How does it discover the JMX processes? How can I do this in a program?
This article shows how to do it using Attach API from JDK tools.jar
Replying since I had this question too and got an answer.
There is a JPS program in JDK which shows java processes.
I am not 100% sure (don't want to dive deep into jconsole code) but 99% sure jconsole uses the same mechanism as jps:
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/tools/jps/Jps.java?av=f
HostIdentifier hostId = arguments.hostId();
MonitoredHost monitoredHost = MonitoredHost.getMonitoredHost(hostId);
// get the set active JVMs on the specified host.
Set<Integer> jvms = monitoredHost.activeVms();
These class are part of tools.jar, you need to include it in the project's classpath.
If we go deeper (I do not expose all the intermediate steps) - finally we'll know that active VMs list is populated from hsperfdata files in temporary directories:
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/jvmstat/perfdata/monitor/protocol/local/PerfDataFile.java
Here is a link to know more:
java hsperfdata directory
At last, here is a code snippet allowing you to get the java processes ids:
sun.jvmstat.monitor.MonitoredHost host = sun.jvmstat.monitor.MonitoredHost.getMonitoredHost(new sun.jvmstat.monitor.HostIdentifier((String) null));
System.out.println(host.activeVms());
P.S.
Then you can use Attach API (as kostya mentioned) to discover the rest of needed things.
After you register the MBean in your application, like this:
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
//register the MBean
ObjectMbean mBean = new ObjectMbean();
ObjectName name = new ObjectName("com.gd.eventfiltering.jmx:type=SystemConfig");
mbs.registerMBean(mBean, name);
Then you can call your MBean like this:
JMXServiceURL url = new JMXServiceURL(JMX_PATH);
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
ObjectName mbeanName = new ObjectName(JMX_OBJECT);
IObjectMbean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName,IObjectMbean.class, false);
//call the method
List<EventType> filters = mbeanProxy.methodFromYourMBean();
jmxc.close();

CDH4, Sqoop2 and JDBC drivers: no suitable driver found

I am trying to use Sqoop 2 to import data from a MySQL database to HDFS, basically following the instructions here. However, the Sqoop server is unable to make a connection to the MySQL database due to appropriate drivers not found.
Setup:
Here is some background on my setup:
Hadoop cluster: I have a three machine Hadoop cluster running CDH 4.4.0. Sqoop 2 was configured through the Cloudera Manager, and is running on the same machine as the Namenode.
I am developing on a Windows machine which is also where my MySQL database lives. The Hadoop cluster is a set of three Ubuntu Server machines.
MySQL database: I have a MySQL database running on my Windows machine, and I have checked that the MySQL database can be accessed from each of the machines in my Hadoop cluster.
Client application: My client application is an Eclipse project on my Windows machine which basically opens up a Sqoop client corresponding to a Sqoop server (I have verified that the Sqoop server and client are running on my Namenode).
Here is the main class of my client application.
package com.fc.SqoopImport;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.sqoop.client.*;
import org.apache.sqoop.*;
import org.apache.sqoop.common.*;
import org.apache.sqoop.model.*;
import org.apache.sqoop.validation.Status;
import com.mysql.jdbc.*;
public class SqoopImport {
// utlity function to cycle through the connector and framework forms for errors
private static void printMessage(List<MForm> formList) {
for(MForm form : formList) {
List<MInput<?>> inputlist = form.getInputs();
if (form.getValidationMessage() != null) {
System.out.println("Form message: " + form.getValidationMessage());
}
for (MInput minput : inputlist) {
if (minput.getValidationStatus() == Status.ACCEPTABLE) {
System.out.println("Warning:" + minput.getValidationMessage());
} else if (minput.getValidationStatus() == Status.UNACCEPTABLE) {
System.out.println("Error:" + minput.getValidationMessage());
}
}
}
}
public static void main(String[] args) throws Exception {
String driver = "com.mysql.jdbc.Driver";
Class.forName(driver);
// location of the server running Sqoop 2 server
String urlSqoop2Server = "http://fc-01.fc.com:12000/sqoop/";
SqoopClient clientSqoop2 = new SqoopClient(urlSqoop2Server);
// dummy connection object
MConnection sqoopConnSAP = clientSqoop2.newConnection(1);
MConnectionForms sqoopConnSAPFrameworkForm = sqoopConnSAP.getFrameworkPart();
MConnectionForms sqoopConnSAPConnForm = sqoopConnSAP.getConnectorPart();
sqoopConnSAP.setName("SqoopConnSAP");
// Set the values for the connection form
sqoopConnSAPConnForm.getStringInput("connection.connectionString").setValue("jdbc:mysql://192.168.31.172:3306/dbsap");
sqoopConnSAPConnForm.getStringInput("connection.jdbcDriver").setValue("com.mysql.jdbc.Driver");
sqoopConnSAPConnForm.getStringInput("connection.username").setValue("root");
sqoopConnSAPConnForm.getStringInput("connection.password").setValue("1234");
sqoopConnSAPFrameworkForm.getIntegerInput("security.maxConnections").setValue(10);
Status statusConnSAP = clientSqoop2.createConnection(sqoopConnSAP);
if(statusConnSAP.canProceed()) {
System.out.println("Created. New connection ID: " + sqoopConnSAP.getPersistenceId());
} else {
System.out.println("Check for status and forms errors.");
printMessage(sqoopConnSAP.getConnectorPart().getForms());
printMessage(sqoopConnSAP.getFrameworkPart().getForms());
}
}
}
Error:
Running this project gives the following error:
Check for status and forms errors.
Form message: Can't connect to the database with given credentials: No suitable driver found for jdbc:mysql:192.168.31.172:3306/dbsap
Error:Can't load specified driver
Diagnosis:
The appropriate JDBC drivers (mysql-connector-java-5.1.26-bin.jar) is part of my Eclipse project, and for good measure, I have added this to the sqoop2 lib folder
/opt/cloudera/parcels/CDH-4.4.0-1.cdh4.4.0.p0.39/lib/sqoop2/client-lib
as well. However, this is the part I am not sure of, since the CDH4 documentation says]1 that in case Sqoop was installed using Cloudera Manager, the location of the appropriate JDBC driver should be added to HADOOP_CLASSPATH. So, I did
export HADOOP_CLASSPATH=/usr/lib/jdbcJars:HADOOP_CLASSPATH;
on my Hadoop Namenode, so that an echo $HADOOP_CLASSPATH gives /usr/lib/jdbcJars. Again, I am not entirely sure of the utility of this since my client application is not being developed on the Hadoop cluster.
The last thing that I have not tried yet is creating a new /usr/lib/sqoop/lib directory and adding the JDBC driver there.
Any help figuring this out would be appreciated.
Never ever alter content of parcel directory (/opt/cloudera/parcels/*). There are always different ways how to configure components. For example based on the official documentation, you need to copy the MySQL JDBC driver into /var/lib/sqoop2 directory on the node where you are running Sqoop2 server.
put the mysql-jdbc-driver into the dir:
/usr/lib/sqoop2/webapps/sqoop/WEB-INF/lib/mysql-connector-java-5.1.25.jar
and restart the sqoop2 server

Oracle: Java stored procedure sending JMS Message

I am attempting to send a Point-to-Point JMS message from an oracle database stored procedure to a java application. The two 'points' sit on different machines, which I've confirmed can talk to each other via ping.
I've created a java application able to successfully take messages off a queue within the application server. The application is running within a JBoss v4.2.3 server. I've been able to successfully send a JMS message from a remote java application, so I'm sure the code running within the server is ok.
I've taken code from the working remote java application and loaded this successfully into an oracle stored procedure. I've also managed to (I believe!) load into oracle the required jar files using the loadjava utility. The three jar files I've loaded in are:
* jms-1.1
* jbossmq-3.2.3
* jboss-client-4.0.2
The three jars are used within the working remote java application and appear to be all that's required. The code contained loaded into the stored procedure is as follows:
package com.base.jms.client;
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
public class StandAloneClient {
public static String send() throws Exception {
String result = "Starting -> ";
try {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
env.put(Context.PROVIDER_URL, "192.168.111.242:1099");
env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
result = result + "Environment -> ";
// set up stuff
Context ic = new InitialContext(env);
result = result + "Context -> ";
QueueConnectionFactory connectionFactory = (QueueConnectionFactory) ic.lookup("ConnectionFactory");
result = result + "Factory -> ";
Queue queue = (Queue) ic.lookup("queue/A");
result = result + "Queue -> ";
QueueConnection connection = connectionFactory.createQueueConnection();
result = result + "Connection -> ";
QueueSession session = connection.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
result = result + "Session -> ";
QueueSender sender = session.createSender(queue);
connection.start();
result = result + "Sender -> ";
TextMessage myMessage = session.createTextMessage();
myMessage.setText(result);
sender.send(myMessage);
result = result + "Sending Message -> ";
sender.close();
session.close();
connection.close();
result = result + "Close";
} catch (JMSException e) {
result = result + "JMS Exception";
/*
if(e.getMessage() != null) {
result = result + ":" + e.getMessage();
}*/
} catch (Exception e) {
result = result + "Exception";
/*
if(e.getMessage() != null) {
result = result + ":" + e.getMessage();
}*/
}
return result;
}
}
I've added the result string in so I can try and determine where in the code it's falling over. To create and test this procedure, I'm executing the following commands in sqlplus:
create or replace function send_jms return VARCHAR2 as language java name 'com.base.jms.client.StandAloneClient.send() return java.lang.String';
variable myString varchar2(20);
call send_jms() into :myString;
Call completed.
print myString;
Everything seems to be loaded and compiling correctly, however the message is not being sent. The result string returned implicates it's falling over when attempting to retrieve the QueueConnectionFactory class from the InitialContext. The result string returned is:
Starting -> Environment -> Context -> Exception
I'm at a loss as to why this is not working, and have been unable to glean more from the Exception thrown. Can anyone confirm that I am doing this correctly, and if I am, see what I am doing wrong?
Apologies for the long post but thank you in advance for looking at it!
I'm not exactly an expert about running Java and JMS within the Oracle database (though I know each of the three components separately). But from your description it seems you haven't taken the Oracle security model for Java into consideration.
Oracle will not let any component access the network (or the file system etc.) without having explicitly being granted the right to. So start reading about Oracle JVM security to learn how you might need to configure Oracle for letting you connect to a remote machine.
Granting the permissions could involve the following statement:
EXEC DBMS_JAVA.GRANT_PERMISSION('YOUR_SCHEMA', 'SYS:java.net.SocketPermission', '192.168.111.242', 'connect,accept,resolve');
I've finally got this all working, thanks in large part to the help offered by Cody. I'm just putting this answer up in case someone else comes across the same problems and wants to know exactly how I solved it.
The statements I ran to grant the necessary permissions to oracle were:
GRANT JAVASYSPRIV to NTIS
EXEC dbms_java.grant_permission ('JAVASYSPRIV', 'SYS:java.net.SocketPermission', '*', 'accept,connect,listen,resolve');
EXEC DBMS_JAVA.GRANT_PERMISSION('YOUR SCHEMA', 'SYS:java.net.SocketPermission', '*', 'connect,accept,resolve');
You have to been logged in as 'SYSTEM' user when executing these commands.
I had some further problems with the stored procedure which were down to issues with the libraries I loaded into oracle. The jars I used were:
jms
jbossmq-client
jboss-client
The jbossmq-client and jboss-client jars must be the same versions as those used in the application server itself. I was using JBoss 4.2.3, so I copied these jars from the 'client' folder within the JBoss directory.
I also had to put the ipaddress referenced in the java class/stored procedure in the hosts file as it appears oracle does a reverse lookup on the ipaddress. If you don't do this you get an unknown host exception. In the hosts file, just put your computers name and the ipaddress.
After doing all this is, its now working correctly for me! Hope this helps someone if they come across the same problem.
I found that even though I was trying to grant permission as SYS, and the policy belonged to SYS, then SYS didn't have permission to grant that policy.
So this worked for me:
exec dbms_java.grant_policy_permission('SYS','SYS','java.net.SocketPermission','*');
exec dbms_java.grant_permission( 'MY_SCHEMA', 'SYS:java.net.SocketPermission', '*', 'connect,resolve' );

How do I programmatically dump JMX data?

I want to be able to log all JMX data accessible via jconsole. Is there a way to do this programmatically? I'm building a form of logging of the system, and I want to create intervaled data viewable with some tool similar to jconsole.
How would I go about doing this?
java.lang.management.ManagementFactory gives you access to JMX data.
i.g.
List<MemoryPoolMXBean> memPoolBeans = ManagementFactory.getMemoryPoolMXBeans();
for (MemoryPoolMXBean mpb : memPoolBeans) {
System.out.println("Memory Pool: " + mpb.getName());
}
Some samples are available at SO query: [java]+managementfactory
A good read: https://www.ibm.com/developerworks/library/j-jtp09196/index.html
For full implementation connecting to a remote VM:
Map<String,String[]> env = new HashMap<String, String[]>();
env.put( JMXConnector.CREDENTIALS, new String[]{"user","pass"} );
JMXServiceURL address = new JMXServiceURL("service:rmi:///jndi/rmi://host:port/jmxrmi");
JMXConnector connector = JMXConnectorFactory.connect(address,env);
MBeanServerConnection mbs = connector.getMBeanServerConnection();
//get all mbeans
Set<ObjectInstance> beans = mbs.queryMBeans(null,null);
for( ObjectInstance instance : beans )
{
MBeanInfo info = mbs.getMBeanInfo( instance.getObjectName() );
}
From the info, you can query object names and attributes as desired.
I used this command line JMX client as a starting point when I wanted to do the same thing.

Categories