1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * ident "%Z%%M% %I% %E% SMI" 27 */ 28 package org.opensolaris.os.dtrace; 29 30 import java.util.*; 31 import java.io.*; 32 import java.beans.*; 33 34 /** 35 * A value generated by the DTrace {@code ustack()} or {@code jstack()} 36 * action. 37 * <p> 38 * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 39 * 40 * @author Tom Erickson 41 */ 42 public final class UserStackRecord implements StackValueRecord, 43 Serializable, Comparable <UserStackRecord> 44 { 45 static final long serialVersionUID = -4195269026915862308L; 46 47 static { 48 try { 49 BeanInfo info = Introspector.getBeanInfo(UserStackRecord.class); 50 PersistenceDelegate persistenceDelegate = 51 new DefaultPersistenceDelegate( 52 new String[] {"processID", "stackFrames", "rawStackData"}) 53 { 54 /* 55 * Need to prevent DefaultPersistenceDelegate from using 56 * overridden equals() method, resulting in a 57 * StackOverFlowError. Revert to PersistenceDelegate 58 * implementation. See 59 * http://forum.java.sun.com/thread.jspa?threadID= 60 * 477019&tstart=135 61 */ 62 protected boolean 63 mutatesTo(Object oldInstance, Object newInstance) 64 { 65 return (newInstance != null && oldInstance != null && 66 oldInstance.getClass() == newInstance.getClass()); 67 } 68 }; 69 BeanDescriptor d = info.getBeanDescriptor(); 70 d.setValue("persistenceDelegate", persistenceDelegate); 71 } catch (IntrospectionException e) { 72 System.out.println(e); 73 } 74 } 75 76 private transient KernelStackRecord stackRecord; 77 /** @serial */ 78 private final int processID; 79 80 /** 81 * Called by native code. 82 */ 83 private 84 UserStackRecord(int pid, byte[] rawBytes) 85 { 86 stackRecord = new KernelStackRecord(rawBytes); 87 processID = pid; 88 validate(); 89 } 90 91 /** 92 * Creates a {@code UserStackRecord} with the given stack frames, 93 * user process ID, and raw stack data. Supports XML persistence. 94 * 95 * @param frames array of human-readable stack frames, copied so 96 * that later modifying the given frames array will not affect this 97 * {@code UserStackRecord}; may be {@code null} or empty to indicate 98 * that the raw stack data was not converted to human-readable stack 99 * frames (see {@link StackValueRecord#getStackFrames()}) 100 * @param pid non-negative user process ID 101 * @param rawBytes array of raw bytes used to represent this stack 102 * value in the native DTrace library, needed to distinguish stacks 103 * that have the same display value but are considered distinct by 104 * DTrace; copied so that later modifying the given array will not 105 * affect this {@code UserStackRecord} 106 * @throws NullPointerException if the given array of raw bytes is 107 * {@code null} or if any element of the {@code frames} array is 108 * {@code null} 109 * @throws IllegalArgumentException if the given process ID is 110 * negative 111 */ 112 public 113 UserStackRecord(int pid, StackFrame[] frames, byte[] rawBytes) 114 { 115 stackRecord = new KernelStackRecord(frames, rawBytes); 116 processID = pid; 117 validate(); 118 } 119 120 private final void 121 validate() 122 { 123 if (processID < 0) { 124 throw new IllegalArgumentException("process ID is negative"); 125 } 126 } 127 128 public StackFrame[] 129 getStackFrames() 130 { 131 return stackRecord.getStackFrames(); 132 } 133 134 void 135 setStackFrames(StackFrame[] frames) 136 { 137 stackRecord.setStackFrames(frames); 138 } 139 140 /** 141 * Gets the native DTrace representation of this record's stack as 142 * an array of raw bytes. The raw bytes include the process ID and 143 * are used in {@link #equals(Object o) equals()} and {@link 144 * #compareTo(UserStackRecord r) compareTo()} to test equality and 145 * to determine the natural ordering of user stack records. 146 * 147 * @return the native DTrace library's internal representation of 148 * this record's stack as a non-null array of bytes 149 */ 150 public byte[] 151 getRawStackData() 152 { 153 return stackRecord.getRawStackData(); 154 } 155 156 /** 157 * Gets the raw bytes used to represent this record's stack value in 158 * the native DTrace library. To get a human-readable 159 * representation, call {@link #toString()}. 160 * 161 * @return {@link #getRawStackData()} 162 */ 163 public Object 164 getValue() 165 { 166 return stackRecord.getValue(); 167 } 168 169 /** 170 * Gets the process ID associated with this record's user stack. 171 * 172 * @return non-negative pid 173 */ 174 public int 175 getProcessID() 176 { 177 return processID; 178 } 179 180 public List <StackFrame> 181 asList() 182 { 183 return stackRecord.asList(); 184 } 185 186 /** 187 * Gets a {@code KernelStackRecord} view of this record. 188 * 189 * @return non-null {@code KernelStackRecord} view of this record 190 */ 191 public KernelStackRecord 192 asKernelStackRecord() 193 { 194 return stackRecord; 195 } 196 197 /** 198 * Compares the specified object with this {@code UserStackRecord} 199 * for equality. Returns {@code true} if and only if the specified 200 * object is also a {@code UserStackRecord} and both stacks have the 201 * same raw stack data (including process ID). 202 * <p> 203 * This implementation first checks if the specified object is this 204 * {@code UserStackRecord}. If so, it returns {@code true}. 205 * 206 * @return {@code true} if and only if the specified object is also 207 * a {@code UserStackRecord} and both stacks have the same raw stack 208 * data (including process ID) 209 */ 210 @Override 211 public boolean 212 equals(Object o) 213 { 214 if (o == this) { 215 return true; 216 } 217 if (o instanceof UserStackRecord) { 218 UserStackRecord r = (UserStackRecord)o; 219 return stackRecord.equals(r.stackRecord); 220 } 221 return false; 222 } 223 224 /** 225 * Overridden to ensure that equal instances have equal hash codes. 226 */ 227 @Override 228 public int 229 hashCode() 230 { 231 return stackRecord.hashCode(); 232 } 233 234 /** 235 * Compares this record with the given {@code UserStackRecord}. 236 * Compares the first unequal pair of bytes at the same index in 237 * each record's raw stack data, or if all corresponding bytes are 238 * equal, compares the length of each record's array of raw stack 239 * data. Corresponding bytes are compared as unsigned values. The 240 * {@code compareTo()} method is compatible with {@link 241 * #equals(Object o) equals()}. 242 * <p> 243 * This implementation first checks if the specified object is this 244 * {@code UserStackRecord}. If so, it returns {@code 0}. 245 * 246 * @return -1, 0, or 1 as this record's raw stack data is less than, 247 * equal to, or greater than the given record's raw stack data 248 */ 249 public int 250 compareTo(UserStackRecord r) 251 { 252 if (r == this) { 253 return 0; 254 } 255 256 return stackRecord.compareTo(r.stackRecord); 257 } 258 259 /** 260 * Serialize this {@code UserStackRecord} instance. 261 * 262 * @serialData Serialized fields are emitted, followed first by this 263 * record's stack frames as an array of type {@link String}, then by 264 * this record's raw stack data as an array of bytes. 265 */ 266 private void 267 writeObject(ObjectOutputStream s) throws IOException 268 { 269 s.defaultWriteObject(); 270 s.writeObject(stackRecord.getStackFrames()); 271 s.writeObject(stackRecord.getRawStackData()); 272 } 273 274 private void 275 readObject(ObjectInputStream s) throws IOException, ClassNotFoundException 276 { 277 s.defaultReadObject(); 278 try { 279 StackFrame[] frames = (StackFrame[])s.readObject(); 280 byte[] rawBytes = (byte[])s.readObject(); 281 // defensively copies stack frames and raw bytes 282 stackRecord = new KernelStackRecord(frames, rawBytes); 283 } catch (Exception e) { 284 InvalidObjectException x = new InvalidObjectException( 285 e.getMessage()); 286 x.initCause(e); 287 throw x; 288 } 289 // check class invariants 290 try { 291 validate(); 292 } catch (Exception e) { 293 InvalidObjectException x = new InvalidObjectException( 294 e.getMessage()); 295 x.initCause(e); 296 throw x; 297 } 298 } 299 300 /** 301 * Gets the {@link KernelStackRecord#toString() string 302 * representation} of the view returned by {@link 303 * #asKernelStackRecord()} preceded by the user process ID on its 304 * own line. The process ID is in the format {@code process ID: 305 * pid} (where <i>pid</i> is a decimal integer) and is indented by 306 * the same number of spaces as the stack frames. The exact format 307 * is subject to change. 308 */ 309 public String 310 toString() 311 { 312 StringBuilder buf = new StringBuilder(); 313 final int stackindent = KernelStackRecord.STACK_INDENT; 314 int i; 315 buf.append('\n'); 316 for (i = 0; i < KernelStackRecord.STACK_INDENT; ++i) { 317 buf.append(' '); 318 } 319 buf.append("process ID: "); 320 buf.append(processID); 321 buf.append(stackRecord.toString()); // starts with newline 322 return buf.toString(); 323 } 324 } 325