/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.pentaho.di.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.job.Job;
import org.pentaho.di.trans.Trans;
public class GetStatusServlet extends BaseHttpServlet implements CartePluginInterface {
private static Class<?> PKG = GetStatusServlet.class; // for i18n purposes, needed by Translator2!!
private static final long serialVersionUID = 3634806745372015720L;
public static final String CONTEXT_PATH = "/kettle/status";
public GetStatusServlet() {
}
public GetStatusServlet( TransformationMap transformationMap, JobMap jobMap ) {
super( transformationMap, jobMap );
}
/**
<div id="mindtouch">
<h1>/kettle/status</h1>
<a name="GET"></a>
<h2>GET</h2>
<p>Retrieve server status. The status contains information about the server itself (OS, memory, etc)
and information about jobs and transformations present on the server.</p>
<p><b>Example Request:</b><br />
<pre function="syntax.xml">
GET /kettle/status/?xml=Y
</pre>
</p>
<h3>Parameters</h3>
<table class="pentaho-table">
<tbody>
<tr>
<th>name</th>
<th>description</th>
<th>type</th>
</tr>
<tr>
<td>xml</td>
<td>Boolean flag which defines output format <code>Y</code> forces XML output to be generated.
HTML is returned otherwise.</td>
<td>boolean, optional</td>
</tr>
</tbody>
</table>
<h3>Response Body</h3>
<table class="pentaho-table">
<tbody>
<tr>
<td align="right">element:</td>
<td>(custom)</td>
</tr>
<tr>
<td align="right">media types:</td>
<td>text/xml, text/html</td>
</tr>
</tbody>
</table>
<p>Response XML or HTML response containing details about the transformation specified.
If an error occurs during method invocation <code>result</code> field of the response
will contain <code>ERROR</code> status.</p>
<p><b>Example Response:</b></p>
<pre function="syntax.xml">
<?xml version="1.0" encoding="UTF-8"?>
<serverstatus>
<statusdesc>Online</statusdesc>
<memory_free>229093440</memory_free>
<memory_total>285736960</memory_total>
<cpu_cores>4</cpu_cores>
<cpu_process_time>7534848300</cpu_process_time>
<uptime>68818403</uptime>
<thread_count>45</thread_count>
<load_avg>-1.0</load_avg>
<os_name>Windows 7</os_name>
<os_version>6.1</os_version>
<os_arch>amd64</os_arch>
<transstatuslist>
<transstatus>
<transname>Row generator test</transname>
<id>56c93d4e-96c1-4fae-92d9-d864b0779845</id>
<status_desc>Waiting</status_desc>
<error_desc/>
<paused>N</paused>
<stepstatuslist>
</stepstatuslist>
<first_log_line_nr>0</first_log_line_nr>
<last_log_line_nr>0</last_log_line_nr>
<logging_string><![CDATA[]]></logging_string>
</transstatus>
<transstatus>
<transname>dummy-trans</transname>
<id>c56961b2-c848-49b8-abde-76c8015e29b0</id>
<status_desc>Stopped</status_desc>
<error_desc/>
<paused>N</paused>
<stepstatuslist>
</stepstatuslist>
<first_log_line_nr>0</first_log_line_nr>
<last_log_line_nr>0</last_log_line_nr>
<logging_string><![CDATA[]]></logging_string>
</transstatus>
</transstatuslist>
<jobstatuslist>
<jobstatus>
<jobname>dummy_job</jobname>
<id>abd61143-8174-4f27-9037-6b22fbd3e229</id>
<status_desc>Stopped</status_desc>
<error_desc/>
<logging_string><![CDATA[]]></logging_string>
<first_log_line_nr>0</first_log_line_nr>
<last_log_line_nr>0</last_log_line_nr>
</jobstatus>
</jobstatuslist>
</serverstatus>
</pre>
<h3>Status Codes</h3>
<table class="pentaho-table">
<tbody>
<tr>
<th>code</th>
<th>description</th>
</tr>
<tr>
<td>200</td>
<td>Request was processed.</td>
</tr>
<tr>
<td>500</td>
<td>Internal server error occurs during request processing.</td>
</tr>
</tbody>
</table>
</div>
*/
public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
IOException {
if ( isJettyMode() && !request.getContextPath().startsWith( CONTEXT_PATH ) ) {
return;
}
if ( log.isDebug() ) {
logDebug( BaseMessages.getString( PKG, "GetStatusServlet.StatusRequested" ) );
}
response.setStatus( HttpServletResponse.SC_OK );
boolean useXML = "Y".equalsIgnoreCase( request.getParameter( "xml" ) );
if ( useXML ) {
response.setContentType( "text/xml" );
response.setCharacterEncoding( Const.XML_ENCODING );
} else {
response.setContentType( "text/html;charset=UTF-8" );
}
PrintWriter out = response.getWriter();
List<CarteObjectEntry> transEntries = getTransformationMap().getTransformationObjects();
List<CarteObjectEntry> jobEntries = getJobMap().getJobObjects();
if ( useXML ) {
out.print( XMLHandler.getXMLHeader( Const.XML_ENCODING ) );
SlaveServerStatus serverStatus = new SlaveServerStatus();
serverStatus.setStatusDescription( "Online" );
getSystemInfo( serverStatus );
for ( CarteObjectEntry entry : transEntries ) {
Trans trans = getTransformationMap().getTransformation( entry );
String status = trans.getStatus();
SlaveServerTransStatus sstatus = new SlaveServerTransStatus( entry.getName(), entry.getId(), status );
sstatus.setLogDate( trans.getLogDate() );
sstatus.setPaused( trans.isPaused() );
serverStatus.getTransStatusList().add( sstatus );
}
for ( CarteObjectEntry entry : jobEntries ) {
Job job = getJobMap().getJob( entry );
String status = job.getStatus();
SlaveServerJobStatus jobStatus = new SlaveServerJobStatus( entry.getName(), entry.getId(), status );
jobStatus.setLogDate( job.getLogDate() );
serverStatus.getJobStatusList().add( jobStatus );
}
try {
out.println( serverStatus.getXML() );
} catch ( KettleException e ) {
throw new ServletException( "Unable to get the server status in XML format", e );
}
} else {
out.println( "<HTML>" );
out.println( "<HEAD><TITLE>"
+ BaseMessages.getString( PKG, "GetStatusServlet.KettleSlaveServerStatus" ) + "</TITLE>" );
out.println( "<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
out.println( "<H1>" + BaseMessages.getString( PKG, "GetStatusServlet.TopStatus" ) + "</H1>" );
try {
out.println( "<table border=\"1\">" );
out.print( "<tr> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.TransName" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.CarteId" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Status" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.LastLogDate" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Remove" ) + "</th> </tr>" );
Comparator<CarteObjectEntry> transComparator = new Comparator<CarteObjectEntry>() {
@Override
public int compare( CarteObjectEntry o1, CarteObjectEntry o2 ) {
Trans t1 = getTransformationMap().getTransformation( o1 );
Trans t2 = getTransformationMap().getTransformation( o2 );
Date d1 = t1.getLogDate();
Date d2 = t2.getLogDate();
// if both transformations have last log date, desc sort by log date
if (d1 != null && d2 != null) {
int logDateCompare = d2.compareTo( d1 );
if ( logDateCompare != 0 ) {
return logDateCompare;
}
}
return o1.compareTo( o2 );
}
};
Collections.sort( transEntries, transComparator );
for ( CarteObjectEntry entry : transEntries ) {
String name = entry.getName();
String id = entry.getId();
Trans trans = getTransformationMap().getTransformation( entry );
String status = trans.getStatus();
String removeText = "";
// Finished, Stopped, Waiting : allow the user to remove the transformation
//
if ( trans.isFinished() || trans.isStopped() || ( !trans.isInitializing() && !trans.isRunning() ) ) {
removeText =
"<a href=\""
+ convertContextPath( RemoveTransServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( name, "UTF-8" ) + "&id=" + id + "\"> Remove </a>";
}
out.print( "<tr>" );
out.print( "<td><a href=\""
+ convertContextPath( GetTransStatusServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( name, "UTF-8" ) + "&id=" + id + "\">" + name + "</a></td>" );
out.print( "<td>" + id + "</td>" );
out.print( "<td>" + status + "</td>" );
out.print( "<td>"
+ ( trans.getLogDate() == null ? "-" : XMLHandler.date2string( trans.getLogDate() ) ) + "</td>" );
out.print( "<td>" + removeText + "</td>" );
out.print( "</tr>" );
}
out.print( "</table><p>" );
out.println( "<table border=\"1\">" );
out.print( "<tr> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.JobName" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.CarteId" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Status" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.LastLogDate" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Remove" ) + "</th> </tr>" );
Comparator<CarteObjectEntry> jobComparator = new Comparator<CarteObjectEntry>() {
@Override
public int compare( CarteObjectEntry o1, CarteObjectEntry o2 ) {
Job t1 = getJobMap().getJob( o1 );
Job t2 = getJobMap().getJob( o2 );
Date d1 = t1.getLogDate();
Date d2 = t2.getLogDate();
// if both jobs have last log date, desc sort by log date
if (d1 != null && d2 != null) {
int logDateCompare = d2.compareTo( d1 );
if ( logDateCompare != 0 ) {
return logDateCompare;
}
}
return o1.compareTo( o2 );
}
};
Collections.sort( jobEntries, jobComparator );
for ( CarteObjectEntry entry : jobEntries ) {
String name = entry.getName();
String id = entry.getId();
Job job = getJobMap().getJob( entry );
String status = job.getStatus();
String removeText;
if ( job.isFinished() || job.isStopped() ) {
removeText =
"<a href=\""
+ convertContextPath( RemoveJobServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( name, "UTF-8" ) + "&id=" + id + "\"> Remove </a>";
} else {
removeText = "";
}
out.print( "<tr>" );
out.print( "<td><a href=\""
+ convertContextPath( GetJobStatusServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( name, "UTF-8" ) + "&id=" + id + "\">" + name + "</a></td>" );
out.print( "<td>" + id + "</td>" );
out.print( "<td>" + status + "</td>" );
out.print( "<td>"
+ ( job.getLogDate() == null ? "-" : XMLHandler.date2string( job.getLogDate() ) ) + "</td>" );
out.print( "<td>" + removeText + "</td>" );
out.print( "</tr>" );
}
out.print( "</table>" );
} catch ( Exception ex ) {
out.println( "<p>" );
out.println( "<pre>" );
ex.printStackTrace( out );
out.println( "</pre>" );
}
out.println( "<p>" );
out.println( "<H1>"
+ BaseMessages.getString( PKG, "GetStatusServlet.ConfigurationDetails.Title" ) + "</H1><p>" );
out.println( "<table border=\"1\">" );
out.print( "<tr> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Parameter.Title" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Value.Title" ) + "</th> </tr>" );
// The max number of log lines in the back-end
//
SlaveServerConfig serverConfig = getTransformationMap().getSlaveServerConfig();
if ( serverConfig != null ) {
String maxLines = "";
if ( serverConfig.getMaxLogLines() == 0 ) {
maxLines = BaseMessages.getString( PKG, "GetStatusServlet.NoLimit" );
} else {
maxLines = serverConfig.getMaxLogLines() + BaseMessages.getString( PKG, "GetStatusServlet.Lines" );
}
out.print( "<tr> <td>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Parameter.MaxLogLines" ) + "</td> <td>" + maxLines
+ "</td> </tr>" );
// The max age of log lines
//
String maxAge = "";
if ( serverConfig.getMaxLogTimeoutMinutes() == 0 ) {
maxAge = BaseMessages.getString( PKG, "GetStatusServlet.NoLimit" );
} else {
maxAge = serverConfig.getMaxLogTimeoutMinutes() + BaseMessages.getString( PKG, "GetStatusServlet.Minutes" );
}
out.print( "<tr> <td>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Parameter.MaxLogLinesAge" ) + "</td> <td>" + maxAge
+ "</td> </tr>" );
// The max age of stale objects
//
String maxObjAge = "";
if ( serverConfig.getObjectTimeoutMinutes() == 0 ) {
maxObjAge = BaseMessages.getString( PKG, "GetStatusServlet.NoLimit" );
} else {
maxObjAge = serverConfig.getObjectTimeoutMinutes() + BaseMessages.getString( PKG, "GetStatusServlet.Minutes" );
}
out.print( "<tr> <td>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Parameter.MaxObjectsAge" ) + "</td> <td>" + maxObjAge
+ "</td> </tr>" );
// The name of the specified repository
//
String repositoryName;
try {
repositoryName = serverConfig.getRepository() != null ? serverConfig.getRepository().getName() : "";
} catch ( Exception e ) {
logError( BaseMessages.getString( PKG, "GetStatusServlet.Parameter.RepositoryName.UnableToConnect",
serverConfig.getRepositoryId() ), e );
repositoryName = BaseMessages.getString( PKG, "GetStatusServlet.Parameter.RepositoryName.UnableToConnect",
serverConfig.getRepositoryId() );
}
out.print( "<tr> <td>"
+ BaseMessages.getString( PKG, "GetStatusServlet.Parameter.RepositoryName" ) + "</td> <td>"
+ repositoryName + "</td> </tr>" );
out.print( "</table>" );
String filename = serverConfig.getFilename();
if ( filename == null ) {
filename = BaseMessages.getString( PKG, "GetStatusServlet.ConfigurationDetails.UsingDefaults" );
}
out
.println( "<i>"
+ BaseMessages.getString( PKG, "GetStatusServlet.ConfigurationDetails.Advice", filename )
+ "</i><br>" );
}
out.println( "</BODY>" );
out.println( "</HTML>" );
}
}
private static void getSystemInfo( SlaveServerStatus serverStatus ) {
OperatingSystemMXBean operatingSystemMXBean =
java.lang.management.ManagementFactory.getOperatingSystemMXBean();
ThreadMXBean threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean();
RuntimeMXBean runtimeMXBean = java.lang.management.ManagementFactory.getRuntimeMXBean();
int cores = Runtime.getRuntime().availableProcessors();
long freeMemory = Runtime.getRuntime().freeMemory();
long totalMemory = Runtime.getRuntime().totalMemory();
String osArch = operatingSystemMXBean.getArch();
String osName = operatingSystemMXBean.getName();
String osVersion = operatingSystemMXBean.getVersion();
double loadAvg = operatingSystemMXBean.getSystemLoadAverage();
int threadCount = threadMXBean.getThreadCount();
long allThreadsCpuTime = 0L;
long[] threadIds = threadMXBean.getAllThreadIds();
for ( int i = 0; i < threadIds.length; i++ ) {
allThreadsCpuTime += threadMXBean.getThreadCpuTime( threadIds[i] );
}
long uptime = runtimeMXBean.getUptime();
serverStatus.setCpuCores( cores );
serverStatus.setCpuProcessTime( allThreadsCpuTime );
serverStatus.setUptime( uptime );
serverStatus.setThreadCount( threadCount );
serverStatus.setLoadAvg( loadAvg );
serverStatus.setOsName( osName );
serverStatus.setOsVersion( osVersion );
serverStatus.setOsArchitecture( osArch );
serverStatus.setMemoryFree( freeMemory );
serverStatus.setMemoryTotal( totalMemory );
}
public String toString() {
return "Status Handler";
}
public String getService() {
return CONTEXT_PATH + " (" + toString() + ")";
}
public String getContextPath() {
return CONTEXT_PATH;
}
}