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
UserStackRecord(int pid, byte[] rawBytes)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
UserStackRecord(int pid, StackFrame[] frames, byte[] rawBytes)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
validate()121     validate()
122     {
123 	if (processID < 0) {
124 	    throw new IllegalArgumentException("process ID is negative");
125 	}
126     }
127 
128     public StackFrame[]
getStackFrames()129     getStackFrames()
130     {
131 	return stackRecord.getStackFrames();
132     }
133 
134     void
setStackFrames(StackFrame[] frames)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[]
getRawStackData()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
getValue()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
getProcessID()175     getProcessID()
176     {
177 	return processID;
178     }
179 
180     public List <StackFrame>
asList()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
asKernelStackRecord()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
equals(Object o)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
hashCode()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
compareTo(UserStackRecord r)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
writeObject(ObjectOutputStream s)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
readObject(ObjectInputStream s)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
toString()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