Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 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 | |
| 15 | package com.google.devtools.build.lib.shell; |
| 16 | |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 17 | import java.time.Duration; |
| 18 | import java.util.Optional; |
| 19 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 20 | /** |
| 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 | */ |
| 32 | public final class TerminationStatus { |
| 33 | |
| 34 | private final int waitResult; |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 35 | private final boolean timedOut; |
| 36 | private final Optional<Duration> wallExecutionTime; |
| 37 | private final Optional<Duration> userExecutionTime; |
| 38 | private final Optional<Duration> systemExecutionTime; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 39 | |
| 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}. |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 88 | * @param timedOut whether the execution timed out |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 89 | */ |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 90 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 111 | this.waitResult = waitResult; |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 112 | this.timedOut = timedOut; |
| 113 | this.wallExecutionTime = wallExecutionTime; |
| 114 | this.userExecutionTime = userExecutionTime; |
| 115 | this.systemExecutionTime = systemExecutionTime; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 116 | } |
| 117 | |
| 118 | /** |
ulfjack | 1ade2c47 | 2017-04-18 12:07:53 +0200 | [diff] [blame] | 119 | * Returns the exit code returned by the subprocess. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 120 | */ |
ulfjack | 1ade2c47 | 2017-04-18 12:07:53 +0200 | [diff] [blame] | 121 | public int getRawExitCode() { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 122 | 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() { |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 143 | return !timedOut && (waitResult < SIGNAL_1 || waitResult > SIGNAL_63); |
Lukacs Berki | 3d97e22 | 2016-08-19 14:40:20 +0000 | [diff] [blame] | 144 | } |
| 145 | |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 146 | /** Returns true if the process timed out. */ |
| 147 | public boolean timedOut() { |
| 148 | return timedOut; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 149 | } |
| 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() { |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 166 | if (exited() || timedOut) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 167 | throw new IllegalStateException("getTerminatingSignal() not defined"); |
| 168 | } |
| 169 | return waitResult - SIGNAL_1 + 1; |
| 170 | } |
| 171 | |
| 172 | /** |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 173 | * 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 203 | * Returns a short string describing the termination status. |
| 204 | * e.g. "Exit 1" or "Hangup". |
| 205 | */ |
| 206 | public String toShortString() { |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 207 | return exited() |
| 208 | ? "Exit " + getExitCode() |
| 209 | : (timedOut ? "Timeout" : getSignalString(getTerminatingSignal())); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 210 | } |
| 211 | |
| 212 | @Override |
| 213 | public String toString() { |
Lukacs Berki | 3d97e22 | 2016-08-19 14:40:20 +0000 | [diff] [blame] | 214 | if (exited()) { |
| 215 | return "Process exited with status " + getExitCode(); |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 216 | } else if (timedOut) { |
Lukacs Berki | 3d97e22 | 2016-08-19 14:40:20 +0000 | [diff] [blame] | 217 | return "Timed out"; |
| 218 | } else { |
| 219 | return "Process terminated by signal " + getTerminatingSignal(); |
| 220 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | @Override |
| 224 | public int hashCode() { |
| 225 | return waitResult; |
| 226 | } |
| 227 | |
| 228 | @Override |
| 229 | public boolean equals(Object other) { |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 230 | 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 | |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 270 | /** Sets or clears the wall execution time. */ |
| 271 | public Builder setWallExecutionTime(Optional<Duration> wallExecutionTime) { |
| 272 | this.wallExecutionTime = wallExecutionTime; |
| 273 | return this; |
| 274 | } |
| 275 | |
Googler | d163431 | 2019-08-26 04:58:37 -0700 | [diff] [blame] | 276 | /** Sets the user execution time. */ |
| 277 | public Builder setUserExecutionTime(Duration userExecutionTime) { |
| 278 | this.userExecutionTime = Optional.of(userExecutionTime); |
| 279 | return this; |
| 280 | } |
| 281 | |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 282 | /** Sets or clears the user execution time. */ |
| 283 | public Builder setUserExecutionTime(Optional<Duration> userExecutionTime) { |
| 284 | this.userExecutionTime = userExecutionTime; |
| 285 | return this; |
| 286 | } |
| 287 | |
Googler | d163431 | 2019-08-26 04:58:37 -0700 | [diff] [blame] | 288 | /** Sets the system execution time. */ |
| 289 | public Builder setSystemExecutionTime(Duration systemExecutionTime) { |
| 290 | this.systemExecutionTime = Optional.of(systemExecutionTime); |
| 291 | return this; |
| 292 | } |
| 293 | |
ruperts | 0510f4a | 2017-11-04 07:58:02 +0100 | [diff] [blame] | 294 | /** 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 311 | } |
| 312 | } |