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 2006 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 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 // raw stack data includes the process ID, but the process 220 // ID passed to the constructor is not validated against the 221 // raw stack data; probably best for this class to test all 222 // of its state without making assumptions 223 return ((processID == r.processID) && 224 stackRecord.equals(r.stackRecord)); 225 } 226 return false; 227 } 228 229 /** 230 * Overridden to ensure that equal instances have equal hash codes. 231 */ 232 @Override 233 public int 234 hashCode() 235 { 236 return (stackRecord.hashCode() + processID); 237 } 238 239 /** 240 * Compares this record with the given {@code UserStackRecord}. 241 * Compares process ID first, then if those are equal, compares the 242 * views returned by {@link #asKernelStackRecord()}. The {@code 243 * compareTo()} method is compatible with {@link #equals(Object o) 244 * equals()}. 245 * <p> 246 * This implementation first checks if the specified object is this 247 * {@code UserStackRecord}. If so, it returns {@code 0}. 248 * 249 * @return -1, 0, or 1 as this record is less than, equal to, or 250 * greater than the given record 251 */ 252 public int 253 compareTo(UserStackRecord r) 254 { 255 if (r == this) { 256 return 0; 257 } 258 259 int cmp = 0; 260 cmp = ((processID < r.processID) ? -1 : 261 ((processID > r.processID) ? 1 : 0)); 262 if (cmp == 0) { 263 cmp = stackRecord.compareTo(r.stackRecord); 264 } 265 return cmp; 266 } 267 268 /** 269 * Serialize this {@code UserStackRecord} instance. 270 * 271 * @serialData Serialized fields are emitted, followed first by this 272 * record's stack frames as an array of type {@link String}, then by 273 * this record's raw stack data as an array of bytes. 274 */ 275 private void 276 writeObject(ObjectOutputStream s) throws IOException 277 { 278 s.defaultWriteObject(); 279 s.writeObject(stackRecord.getStackFrames()); 280 s.writeObject(stackRecord.getRawStackData()); 281 } 282 283 private void 284 readObject(ObjectInputStream s) throws IOException, ClassNotFoundException 285 { 286 s.defaultReadObject(); 287 try { 288 StackFrame[] frames = (StackFrame[])s.readObject(); 289 byte[] rawBytes = (byte[])s.readObject(); 290 // defensively copies stack frames and raw bytes 291 stackRecord = new KernelStackRecord(frames, rawBytes); 292 } catch (Exception e) { 293 throw new InvalidObjectException(e.getMessage()); 294 } 295 // check class invariants 296 try { 297 validate(); 298 } catch (Exception e) { 299 throw new InvalidObjectException(e.getMessage()); 300 } 301 } 302 303 /** 304 * Gets the {@link KernelStackRecord#toString() string 305 * representation} of the view returned by {@link 306 * #asKernelStackRecord()} preceded by the user process ID on its 307 * own line. The process ID is in the format {@code process ID: 308 * pid} (where <i>pid</i> is a decimal integer) and is indented by 309 * the same number of spaces as the stack frames. The exact format 310 * is subject to change. 311 */ 312 public String 313 toString() 314 { 315 StringBuffer buf = new StringBuffer(); 316 final int stackindent = KernelStackRecord.STACK_INDENT; 317 int i; 318 buf.append('\n'); 319 for (i = 0; i < KernelStackRecord.STACK_INDENT; ++i) { 320 buf.append(' '); 321 } 322 buf.append("process ID: "); 323 buf.append(processID); 324 buf.append(stackRecord.toString()); // starts with newline 325 return buf.toString(); 326 } 327 } 328