blob: 7c520e4a78c3bd179063fa1a6c5ceb5fae016e5f [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.shell;
16
ruperts0510f4a2017-11-04 07:58:02 +010017import java.time.Duration;
18import java.util.Optional;
19
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020/**
21 * Represents the termination status of a command. {@link Process#waitFor} is
22 * not very precisely specified, so this class encapsulates the interpretation
23 * of values returned by it.
24 *
25 * Caveat: due to the lossy encoding, it's not always possible to accurately
26 * distinguish signal and exit cases. In particular, processes that exit with
27 * a value within the interval [129, 191] will be mistaken for having been
28 * terminated by a signal.
29 *
30 * Instances are immutable.
31 */
32public final class TerminationStatus {
33
34 private final int waitResult;
ruperts0510f4a2017-11-04 07:58:02 +010035 private final boolean timedOut;
36 private final Optional<Duration> wallExecutionTime;
37 private final Optional<Duration> userExecutionTime;
38 private final Optional<Duration> systemExecutionTime;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010039
40 /**
41 * Values taken from the glibc strsignal(3) function.
42 */
43 private static final String[] SIGNAL_STRINGS = {
44 null,
45 "Hangup",
46 "Interrupt",
47 "Quit",
48 "Illegal instruction",
49 "Trace/breakpoint trap",
50 "Aborted",
51 "Bus error",
52 "Floating point exception",
53 "Killed",
54 "User defined signal 1",
55 "Segmentation fault",
56 "User defined signal 2",
57 "Broken pipe",
58 "Alarm clock",
59 "Terminated",
60 "Stack fault",
61 "Child exited",
62 "Continued",
63 "Stopped (signal)",
64 "Stopped",
65 "Stopped (tty input)",
66 "Stopped (tty output)",
67 "Urgent I/O condition",
68 "CPU time limit exceeded",
69 "File size limit exceeded",
70 "Virtual timer expired",
71 "Profiling timer expired",
72 "Window changed",
73 "I/O possible",
74 "Power failure",
75 "Bad system call",
76 };
77
78 private static String getSignalString(int signum) {
79 return signum > 0 && signum < SIGNAL_STRINGS.length
80 ? SIGNAL_STRINGS[signum]
81 : "Signal " + signum;
82 }
83
84 /**
85 * Construct a TerminationStatus instance from a Process waitFor code.
86 *
87 * @param waitResult the value returned by {@link java.lang.Process#waitFor}.
ruperts0510f4a2017-11-04 07:58:02 +010088 * @param timedOut whether the execution timed out
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089 */
ruperts0510f4a2017-11-04 07:58:02 +010090 public TerminationStatus(int waitResult, boolean timedOut) {
91 this(waitResult, timedOut, Optional.empty(), Optional.empty(), Optional.empty());
92 }
93
94 /**
95 * Construct a TerminationStatus instance from a Process waitFor code.
96 *
97 * <p>TerminationStatus objects are considered equal if they have the same waitResult.
98 *
99 * @param waitResult the value returned by {@link java.lang.Process#waitFor}.
100 * @param timedOut whether the execution timed out
101 * @param wallExecutionTime the wall execution time of the command, if available
102 * @param userExecutionTime the user execution time of the command, if available
103 * @param systemExecutionTime the system execution time of the command, if available
104 */
105 public TerminationStatus(
106 int waitResult,
107 boolean timedOut,
108 Optional<Duration> wallExecutionTime,
109 Optional<Duration> userExecutionTime,
110 Optional<Duration> systemExecutionTime) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100111 this.waitResult = waitResult;
ruperts0510f4a2017-11-04 07:58:02 +0100112 this.timedOut = timedOut;
113 this.wallExecutionTime = wallExecutionTime;
114 this.userExecutionTime = userExecutionTime;
115 this.systemExecutionTime = systemExecutionTime;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100116 }
117
118 /**
ulfjack1ade2c472017-04-18 12:07:53 +0200119 * Returns the exit code returned by the subprocess.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100120 */
ulfjack1ade2c472017-04-18 12:07:53 +0200121 public int getRawExitCode() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100122 return waitResult;
123 }
124
125 /**
126 * Returns true iff the process exited with code 0.
127 */
128 public boolean success() {
129 return exited() && getExitCode() == 0;
130 }
131
132 // We're relying on undocumented behaviour of Process.waitFor, specifically
133 // that waitResult is the exit status when the process returns normally, or
134 // 128+signalnumber when the process is terminated by a signal. We further
135 // assume that value signal numbers fall in the interval [1, 63].
136 private static final int SIGNAL_1 = 128 + 1;
137 private static final int SIGNAL_63 = 128 + 63;
138
139 /**
140 * Returns true iff the process exited normally.
141 */
142 public boolean exited() {
ruperts0510f4a2017-11-04 07:58:02 +0100143 return !timedOut && (waitResult < SIGNAL_1 || waitResult > SIGNAL_63);
Lukacs Berki3d97e222016-08-19 14:40:20 +0000144 }
145
ruperts0510f4a2017-11-04 07:58:02 +0100146 /** Returns true if the process timed out. */
147 public boolean timedOut() {
148 return timedOut;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100149 }
150
151 /**
152 * Returns the exit code of the subprocess. Undefined if exited() is false.
153 */
154 public int getExitCode() {
155 if (!exited()) {
156 throw new IllegalStateException("getExitCode() not defined");
157 }
158 return waitResult;
159 }
160
161 /**
162 * Returns the number of the signal that terminated the process. Undefined
163 * if exited() returns true.
164 */
165 public int getTerminatingSignal() {
ruperts0510f4a2017-11-04 07:58:02 +0100166 if (exited() || timedOut) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100167 throw new IllegalStateException("getTerminatingSignal() not defined");
168 }
169 return waitResult - SIGNAL_1 + 1;
170 }
171
172 /**
ruperts0510f4a2017-11-04 07:58:02 +0100173 * Returns the wall execution time.
174 *
175 * @return the measurement, or empty in case of execution errors or when the measurement is not
176 * implemented for the current platform
177 */
178 public Optional<Duration> getWallExecutionTime() {
179 return wallExecutionTime;
180 }
181
182 /**
183 * Returns the user execution time.
184 *
185 * @return the measurement, or empty in case of execution errors or when the measurement is not
186 * implemented for the current platform
187 */
188 public Optional<Duration> getUserExecutionTime() {
189 return userExecutionTime;
190 }
191
192 /**
193 * Returns the system execution time.
194 *
195 * @return the measurement, or empty in case of execution errors or when the measurement is not
196 * implemented for the current platform
197 */
198 public Optional<Duration> getSystemExecutionTime() {
199 return systemExecutionTime;
200 }
201
202 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100203 * Returns a short string describing the termination status.
204 * e.g. "Exit 1" or "Hangup".
205 */
206 public String toShortString() {
ruperts0510f4a2017-11-04 07:58:02 +0100207 return exited()
208 ? "Exit " + getExitCode()
209 : (timedOut ? "Timeout" : getSignalString(getTerminatingSignal()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100210 }
211
212 @Override
213 public String toString() {
Lukacs Berki3d97e222016-08-19 14:40:20 +0000214 if (exited()) {
215 return "Process exited with status " + getExitCode();
ruperts0510f4a2017-11-04 07:58:02 +0100216 } else if (timedOut) {
Lukacs Berki3d97e222016-08-19 14:40:20 +0000217 return "Timed out";
218 } else {
219 return "Process terminated by signal " + getTerminatingSignal();
220 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100221 }
222
223 @Override
224 public int hashCode() {
225 return waitResult;
226 }
227
228 @Override
229 public boolean equals(Object other) {
ruperts0510f4a2017-11-04 07:58:02 +0100230 return other instanceof TerminationStatus
231 && ((TerminationStatus) other).waitResult == this.waitResult;
232 }
233
234 /** Returns a new {@link TerminationStatus.Builder}. */
235 public static Builder builder() {
236 return new TerminationStatus.Builder();
237 }
238
239 /** Builder for {@link TerminationStatus} objects. */
240 public static class Builder {
241 // We use nullness here instead of Optional to avoid confusion between fields that may not
242 // yet have been set from fields that can legitimately hold an Optional value in the built
243 // object.
244
245 private Integer waitResponse = null;
246 private Boolean timedOut = null;
247
248 private Optional<Duration> wallExecutionTime = Optional.empty();
249 private Optional<Duration> userExecutionTime = Optional.empty();
250 private Optional<Duration> systemExecutionTime = Optional.empty();
251
252 /** Sets the value returned by {@link java.lang.Process#waitFor}. */
253 public Builder setWaitResponse(int waitResponse) {
254 this.waitResponse = waitResponse;
255 return this;
256 }
257
258 /** Sets whether the action timed out or not. */
259 public Builder setTimedOut(boolean timedOut) {
260 this.timedOut = timedOut;
261 return this;
262 }
263
264 /** Sets the wall execution time. */
265 public Builder setWallExecutionTime(Duration wallExecutionTime) {
266 this.wallExecutionTime = Optional.of(wallExecutionTime);
267 return this;
268 }
269
ruperts0510f4a2017-11-04 07:58:02 +0100270 /** Sets or clears the wall execution time. */
271 public Builder setWallExecutionTime(Optional<Duration> wallExecutionTime) {
272 this.wallExecutionTime = wallExecutionTime;
273 return this;
274 }
275
Googlerd1634312019-08-26 04:58:37 -0700276 /** Sets the user execution time. */
277 public Builder setUserExecutionTime(Duration userExecutionTime) {
278 this.userExecutionTime = Optional.of(userExecutionTime);
279 return this;
280 }
281
ruperts0510f4a2017-11-04 07:58:02 +0100282 /** Sets or clears the user execution time. */
283 public Builder setUserExecutionTime(Optional<Duration> userExecutionTime) {
284 this.userExecutionTime = userExecutionTime;
285 return this;
286 }
287
Googlerd1634312019-08-26 04:58:37 -0700288 /** Sets the system execution time. */
289 public Builder setSystemExecutionTime(Duration systemExecutionTime) {
290 this.systemExecutionTime = Optional.of(systemExecutionTime);
291 return this;
292 }
293
ruperts0510f4a2017-11-04 07:58:02 +0100294 /** Sets or clears the system execution time. */
295 public Builder setSystemExecutionTime(Optional<Duration> systemExecutionTime) {
296 this.systemExecutionTime = systemExecutionTime;
297 return this;
298 }
299
300 /** Builds a {@link TerminationStatus} object. */
301 public TerminationStatus build() {
302 if (waitResponse == null) {
303 throw new IllegalStateException("waitResponse never set");
304 }
305 if (timedOut == null) {
306 throw new IllegalStateException("timedOut never set");
307 }
308 return new TerminationStatus(
309 waitResponse, timedOut, wallExecutionTime, userExecutionTime, systemExecutionTime);
310 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100311 }
312}