|
@@ -2,10 +2,10 @@ package org.apache.hadoop.yarn.webapp.log;
|
|
|
|
|
|
import static org.apache.hadoop.yarn.webapp.YarnWebParams.APP_OWNER;
|
|
|
import static org.apache.hadoop.yarn.webapp.YarnWebParams.CONTAINER_ID;
|
|
|
+import static org.apache.hadoop.yarn.webapp.YarnWebParams.CONTAINER_LOG_TYPE;
|
|
|
import static org.apache.hadoop.yarn.webapp.YarnWebParams.ENTITY_STRING;
|
|
|
import static org.apache.hadoop.yarn.webapp.YarnWebParams.NM_NODENAME;
|
|
|
|
|
|
-import java.io.DataInputStream;
|
|
|
import java.io.FileNotFoundException;
|
|
|
import java.io.IOException;
|
|
|
import java.util.Map;
|
|
@@ -19,10 +19,11 @@ import org.apache.hadoop.yarn.api.records.ContainerId;
|
|
|
import org.apache.hadoop.yarn.api.records.NodeId;
|
|
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
|
|
import org.apache.hadoop.yarn.logaggregation.AggregatedLogFormat;
|
|
|
-import org.apache.hadoop.yarn.logaggregation.AggregatedLogFormat.LogKey;
|
|
|
import org.apache.hadoop.yarn.logaggregation.LogAggregationUtils;
|
|
|
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
|
|
import org.apache.hadoop.yarn.util.ConverterUtils;
|
|
|
+import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
|
|
+import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.PRE;
|
|
|
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
|
|
|
|
|
import com.google.inject.Inject;
|
|
@@ -41,8 +42,9 @@ public class AggregatedLogsBlock extends HtmlBlock {
|
|
|
ContainerId containerId = verifyAndGetContainerId(html);
|
|
|
NodeId nodeId = verifyAndGetNodeId(html);
|
|
|
String appOwner = verifyAndGetAppOwner(html);
|
|
|
+ LogLimits logLimits = verifyAndGetLogLimits(html);
|
|
|
if (containerId == null || nodeId == null || appOwner == null
|
|
|
- || appOwner.isEmpty()) {
|
|
|
+ || appOwner.isEmpty() || logLimits == null) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -113,24 +115,29 @@ public class AggregatedLogsBlock extends HtmlBlock {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- DataInputStream valueStream;
|
|
|
- LogKey key = new LogKey();
|
|
|
+ String desiredLogType = $(CONTAINER_LOG_TYPE);
|
|
|
try {
|
|
|
- valueStream = reader.next(key);
|
|
|
- while (valueStream != null
|
|
|
- && !key.toString().equals(containerId.toString())) {
|
|
|
- valueStream = reader.next(key);
|
|
|
- }
|
|
|
- if (valueStream == null) {
|
|
|
+ AggregatedLogFormat.ContainerLogsReader logReader =
|
|
|
+ reader.getContainerLogsReader(containerId);
|
|
|
+ if (logReader == null) {
|
|
|
html.h1()._(
|
|
|
"Logs not available for " + logEntity
|
|
|
+ ". Could be caused by the rentention policy")._();
|
|
|
return;
|
|
|
}
|
|
|
- writer().write("<pre>");
|
|
|
- AggregatedLogFormat.LogReader.readAcontainerLogs(valueStream, writer());
|
|
|
- writer().write("</pre>");
|
|
|
- return;
|
|
|
+
|
|
|
+ boolean foundLog = readContainerLogs(html, logReader, logLimits,
|
|
|
+ desiredLogType);
|
|
|
+
|
|
|
+ if (!foundLog) {
|
|
|
+ if (desiredLogType.isEmpty()) {
|
|
|
+ html.h1("No logs available for container " + containerId.toString());
|
|
|
+ } else {
|
|
|
+ html.h1("Unable to locate '" + desiredLogType
|
|
|
+ + "' log for container " + containerId.toString());
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
} catch (IOException e) {
|
|
|
html.h1()._("Error getting logs for " + logEntity)._();
|
|
|
LOG.error("Error getting logs for " + logEntity, e);
|
|
@@ -138,6 +145,76 @@ public class AggregatedLogsBlock extends HtmlBlock {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private boolean readContainerLogs(Block html,
|
|
|
+ AggregatedLogFormat.ContainerLogsReader logReader, LogLimits logLimits,
|
|
|
+ String desiredLogType) throws IOException {
|
|
|
+ int bufferSize = 65536;
|
|
|
+ char[] cbuf = new char[bufferSize];
|
|
|
+
|
|
|
+ boolean foundLog = false;
|
|
|
+ String logType = logReader.nextLog();
|
|
|
+ while (logType != null) {
|
|
|
+ if (desiredLogType == null || desiredLogType.isEmpty()
|
|
|
+ || desiredLogType.equals(logType)) {
|
|
|
+ long logLength = logReader.getCurrentLogLength();
|
|
|
+
|
|
|
+ if (foundLog) {
|
|
|
+ html.pre()._("\n\n")._();
|
|
|
+ }
|
|
|
+
|
|
|
+ html.p()._("Log Type: " + logType)._();
|
|
|
+ html.p()._("Log Length: " + Long.toString(logLength))._();
|
|
|
+
|
|
|
+ long start = logLimits.start < 0
|
|
|
+ ? logLength + logLimits.start : logLimits.start;
|
|
|
+ start = start < 0 ? 0 : start;
|
|
|
+ start = start > logLength ? logLength : start;
|
|
|
+ long end = logLimits.end < 0
|
|
|
+ ? logLength + logLimits.end : logLimits.end;
|
|
|
+ end = end < 0 ? 0 : end;
|
|
|
+ end = end > logLength ? logLength : end;
|
|
|
+ end = end < start ? start : end;
|
|
|
+
|
|
|
+ long toRead = end - start;
|
|
|
+ if (toRead < logLength) {
|
|
|
+ html.p()._("Showing " + toRead + " bytes of " + logLength
|
|
|
+ + " total. Click ")
|
|
|
+ .a(url("logs", $(NM_NODENAME), $(CONTAINER_ID),
|
|
|
+ $(ENTITY_STRING), $(APP_OWNER),
|
|
|
+ logType, "?start=0"), "here").
|
|
|
+ _(" for the full log.")._();
|
|
|
+ }
|
|
|
+
|
|
|
+ long totalSkipped = 0;
|
|
|
+ while (totalSkipped < start) {
|
|
|
+ long ret = logReader.skip(start - totalSkipped);
|
|
|
+ if (ret < 0) {
|
|
|
+ throw new IOException( "Premature EOF from container log");
|
|
|
+ }
|
|
|
+ totalSkipped += ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ int len = 0;
|
|
|
+ int currentToRead = toRead > bufferSize ? bufferSize : (int) toRead;
|
|
|
+ PRE<Hamlet> pre = html.pre();
|
|
|
+
|
|
|
+ while (toRead > 0
|
|
|
+ && (len = logReader.read(cbuf, 0, currentToRead)) > 0) {
|
|
|
+ pre._(new String(cbuf, 0, len));
|
|
|
+ toRead = toRead - len;
|
|
|
+ currentToRead = toRead > bufferSize ? bufferSize : (int) toRead;
|
|
|
+ }
|
|
|
+
|
|
|
+ pre._();
|
|
|
+ foundLog = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ logType = logReader.nextLog();
|
|
|
+ }
|
|
|
+
|
|
|
+ return foundLog;
|
|
|
+ }
|
|
|
+
|
|
|
private ContainerId verifyAndGetContainerId(Block html) {
|
|
|
String containerIdStr = $(CONTAINER_ID);
|
|
|
if (containerIdStr == null || containerIdStr.isEmpty()) {
|
|
@@ -180,4 +257,44 @@ public class AggregatedLogsBlock extends HtmlBlock {
|
|
|
}
|
|
|
return appOwner;
|
|
|
}
|
|
|
+
|
|
|
+ private static class LogLimits {
|
|
|
+ long start;
|
|
|
+ long end;
|
|
|
+ }
|
|
|
+
|
|
|
+ private LogLimits verifyAndGetLogLimits(Block html) {
|
|
|
+ long start = -4096;
|
|
|
+ long end = Long.MAX_VALUE;
|
|
|
+ boolean isValid = true;
|
|
|
+
|
|
|
+ String startStr = $("start");
|
|
|
+ if (startStr != null && !startStr.isEmpty()) {
|
|
|
+ try {
|
|
|
+ start = Long.parseLong(startStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ isValid = false;
|
|
|
+ html.h1()._("Invalid log start value: " + startStr)._();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String endStr = $("end");
|
|
|
+ if (endStr != null && !endStr.isEmpty()) {
|
|
|
+ try {
|
|
|
+ end = Long.parseLong(endStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ isValid = false;
|
|
|
+ html.h1()._("Invalid log end value: " + endStr)._();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isValid) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ LogLimits limits = new LogLimits();
|
|
|
+ limits.start = start;
|
|
|
+ limits.end = end;
|
|
|
+ return limits;
|
|
|
+ }
|
|
|
}
|