Skip to content

Commit 86323eb

Browse files
authored
Don't buffer logs in memory when viewing log as plain text (#706)
1 parent 7594312 commit 86323eb

File tree

4 files changed

+26
-77
lines changed

4 files changed

+26
-77
lines changed

pom.xml

-14
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,4 @@
143143
<url>https://repo.jenkins-ci.org/public/</url>
144144
</pluginRepository>
145145
</pluginRepositories>
146-
147-
<build>
148-
<plugins>
149-
<plugin>
150-
<groupId>org.jenkins-ci.tools</groupId>
151-
<artifactId>maven-hpi-plugin</artifactId>
152-
<configuration>
153-
<loggers>
154-
<io.jenkins.plugins.pipelinegraphview>FINEST</io.jenkins.plugins.pipelinegraphview>
155-
</loggers>
156-
</configuration>
157-
</plugin>
158-
</plugins>
159-
</build>
160146
</project>

src/main/java/io/jenkins/plugins/pipelinegraphview/consoleview/PipelineConsoleViewAction.java

+20-26
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import io.jenkins.plugins.pipelinegraphview.utils.PipelineStepApi;
1717
import io.jenkins.plugins.pipelinegraphview.utils.PipelineStepList;
1818
import java.io.IOException;
19-
import java.io.Writer;
2019
import java.util.ArrayList;
2120
import java.util.HashMap;
2221
import java.util.List;
@@ -28,8 +27,6 @@
2827
import org.kohsuke.stapler.StaplerRequest2;
2928
import org.kohsuke.stapler.StaplerResponse2;
3029
import org.kohsuke.stapler.WebMethod;
31-
import org.kohsuke.stapler.framework.io.CharSpool;
32-
import org.kohsuke.stapler.framework.io.LineEndNormalizingWriter;
3330
import org.kohsuke.stapler.verb.GET;
3431
import org.slf4j.Logger;
3532
import org.slf4j.LoggerFactory;
@@ -120,45 +117,42 @@ protected JSONObject getAllSteps() throws IOException {
120117
}
121118

122119
@WebMethod(name = "log")
123-
public HttpResponse getConsoleText(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException {
120+
public void getConsoleText(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException {
124121
String nodeId = req.getParameter("nodeId");
125122
if (nodeId == null) {
126123
logger.error("'consoleText' was not passed 'nodeId'.");
127-
return HttpResponses.errorJSON("Error getting console text");
124+
rsp.getWriter().write("Error getting console text\n");
125+
return;
128126
}
129127
logger.debug("getConsoleText was passed node id '{}'.", nodeId);
130128
// This will be a step, so return its log output.
131129
AnnotatedLargeText<? extends FlowNode> logText = getLogForNode(nodeId);
130+
if (logText != null) {
131+
logText.writeLogTo(0L, rsp.getOutputStream());
132+
return;
133+
}
132134

133-
long count = 0;
135+
// Potentially a stage, so get the log text for the stage.
136+
boolean foundLogs = false;
134137
PipelineStepList steps = stepApi.getSteps(nodeId);
135-
try (CharSpool spool = new CharSpool()) {
136-
137-
for (PipelineStep step : steps.getSteps()) {
138-
AnnotatedLargeText<? extends FlowNode> logForNode = getLogForNode(String.valueOf(step.getId()));
139-
if (logForNode != null) {
140-
count += logForNode.writeLogTo(0, spool);
141-
}
142-
}
143-
144-
if (count > 0) {
145-
rsp.setContentType("text/plain;charset=UTF-8");
146-
try (Writer writer = rsp.getWriter()) {
147-
spool.flush();
148-
spool.writeTo(new LineEndNormalizingWriter(writer));
149-
}
138+
for (PipelineStep step : steps.getSteps()) {
139+
logText = getLogForNode(step.getId());
140+
if (logText != null) {
141+
foundLogs = true;
142+
logText.writeLogTo(0L, rsp.getOutputStream());
150143
}
151144
}
152-
153-
if (logText != null) {
154-
return HttpResponses.text(PipelineNodeUtil.convertLogToString(logText));
145+
if (!foundLogs) {
146+
rsp.getWriter().write("No logs found\n");
155147
}
156-
return HttpResponses.text("No logs found");
157148
}
158149

159150
/*
160151
* The default behavior of this functions differs from 'getConsoleOutput' in that it will use LOG_THRESHOLD from the end of the string.
161152
* Note: if 'startByte' is negative and falls outside of the console text then we will start from byte 0.
153+
*
154+
* FIXME: This is not performant and needs to be re-written to not buffer in memory. Avoiding JSON for log text.
155+
*
162156
* Example:
163157
* {
164158
* "startByte": 0,
@@ -215,7 +209,7 @@ protected JSONObject getConsoleOutputJson(String nodeId, Long requestStartByte)
215209
startByte = requestStartByte;
216210
}
217211
logger.debug("Returning '{}' bytes from 'getConsoleOutput'.", textLength - startByte);
218-
text = PipelineNodeUtil.convertLogToString(logText, startByte, true);
212+
text = PipelineNodeUtil.convertLogToString(logText, startByte);
219213
endByte = textLength;
220214
}
221215
// If has an exception, return the exception text (inc. stacktrace).

src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineNodeUtil.java

+6-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import edu.umd.cs.findbugs.annotations.CheckForNull;
55
import edu.umd.cs.findbugs.annotations.NonNull;
66
import edu.umd.cs.findbugs.annotations.Nullable;
7-
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
7+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
88
import hudson.AbortException;
99
import hudson.console.AnnotatedLargeText;
1010
import hudson.model.Action;
@@ -292,18 +292,7 @@ public static AnnotatedLargeText<? extends FlowNode> getLogText(@Nullable FlowNo
292292

293293
/*
294294
* Get the generated log text for a given node.
295-
*
296-
* @param log The AnnotatedLargeText object for a given node.
297-
*
298-
* @return The AnnotatedLargeText object representing the log text for this
299-
* node, or null.
300-
*/
301-
public static String convertLogToString(AnnotatedLargeText<? extends FlowNode> log) throws IOException {
302-
return convertLogToString(log, 0L, false);
303-
}
304-
305-
/*
306-
* Get the generated log text for a given node.
295+
* FIXME: This is not performant and needs to be re-written to not buffer in memory.
307296
*
308297
* @param log The AnnotatedLargeText object for a given node.
309298
*
@@ -312,17 +301,14 @@ public static String convertLogToString(AnnotatedLargeText<? extends FlowNode> l
312301
* @return The AnnotatedLargeText object representing the log text for this
313302
* node, or null.
314303
*/
315-
@SuppressWarnings("RV_RETURN_VALUE_IGNORED")
316-
public static String convertLogToString(AnnotatedLargeText<? extends FlowNode> log, Long startByte, boolean html)
304+
@SuppressWarnings("ResultOfMethodCallIgnored")
305+
@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED")
306+
public static String convertLogToString(AnnotatedLargeText<? extends FlowNode> log, Long startByte)
317307
throws IOException {
318308
Writer stringWriter = new StringBuilderWriter();
319309
// NOTE: This returns the total length of the console log, not the received
320310
// bytes.
321-
if (html) {
322-
log.writeHtmlTo(startByte, stringWriter);
323-
} else {
324-
log.writeLogTo(startByte, stringWriter);
325-
}
311+
log.writeHtmlTo(startByte, stringWriter);
326312
return stringWriter.toString();
327313
}
328314

src/test/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineNodeUtilTest.java

-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import static org.junit.Assert.assertFalse;
66
import static org.junit.Assert.assertTrue;
77

8-
import hudson.console.AnnotatedLargeText;
98
import hudson.model.Result;
109
import io.jenkins.plugins.pipelinegraphview.treescanner.PipelineNodeGraphAdapter;
1110
import java.util.List;
@@ -19,22 +18,6 @@
1918
@WithJenkins
2019
class PipelineNodeUtilTest {
2120

22-
@Issue("GH#224")
23-
@Test
24-
void canGetLogTextFromStep(JenkinsRule j) throws Exception {
25-
WorkflowRun run = TestUtils.createAndRunJob(
26-
j, "hello_world_scripted", "helloWorldScriptedPipeline.jenkinsfile", Result.SUCCESS);
27-
28-
PipelineNodeGraphAdapter builder = new PipelineNodeGraphAdapter(run);
29-
String stageId =
30-
TestUtils.getNodesByDisplayName(run, "Say Hello").get(0).getId();
31-
List<FlowNodeWrapper> stepNodes = builder.getStageSteps(stageId);
32-
FlowNodeWrapper echoStep = stepNodes.get(0);
33-
AnnotatedLargeText<? extends FlowNode> logText = PipelineNodeUtil.getLogText(echoStep.getNode());
34-
String logString = PipelineNodeUtil.convertLogToString(logText);
35-
assertThat(logString, equalTo("Hello, World!" + System.lineSeparator()));
36-
}
37-
3821
@Issue("GH#224")
3922
@Test
4023
void canGetErrorTextFromStep(JenkinsRule j) throws Exception {

0 commit comments

Comments
 (0)