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.beans.*; 32 import java.io.*; 33 34 /** 35 * A consistent snapshot of all aggregations requested by a single 36 * {@link Consumer}. 37 * <p> 38 * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 39 * 40 * @see Consumer#getAggregate() 41 * 42 * @author Tom Erickson 43 */ 44 public final class Aggregate implements Serializable 45 { 46 static final long serialVersionUID = 3180340417154076628L; 47 48 static { 49 try { 50 BeanInfo info = Introspector.getBeanInfo(Aggregate.class); 51 PersistenceDelegate persistenceDelegate = 52 new DefaultPersistenceDelegate( 53 new String[] {"snaptime", "aggregations"}); 54 BeanDescriptor d = info.getBeanDescriptor(); 55 d.setValue("persistenceDelegate", persistenceDelegate); 56 } catch (IntrospectionException e) { 57 System.out.println(e); 58 } 59 } 60 61 /** @serial */ 62 private final long snaptime; 63 64 // Map must not have same name as named PersistenceDelegate property 65 // ("aggregations"), otherwise it gets confused for a bean property 66 // and XMLDecoder calls the constructor with a Map instead of the 67 // value of the getAggregations() method. 68 69 private transient Map <String, Aggregation> map; 70 71 /** 72 * Called by native code. 73 */ 74 private 75 Aggregate(long snaptimeNanos) 76 { 77 snaptime = snaptimeNanos; 78 map = new HashMap <String, Aggregation> (); 79 } 80 81 /** 82 * Creates an aggregate with the given snaptime and aggregations. 83 * Supports XML persistence. 84 * 85 * @param snaptimeNanos nanosecond timestamp when this aggregate was 86 * snapped 87 * @param aggregations unordered collection of aggregations 88 * belonging to this aggregate 89 * @throws NullPointerException if the given collection of 90 * aggregations is {@code null} 91 */ 92 public 93 Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations) 94 { 95 snaptime = snaptimeNanos; 96 mapAggregations(aggregations); 97 } 98 99 // assumes map is not yet created 100 private void 101 mapAggregations(Collection <Aggregation> aggregations) 102 { 103 int capacity = (int)(((float)aggregations.size() * 3.0f) / 2.0f); 104 // avoid rehashing and optimize lookup; will never be modified 105 map = new HashMap <String, Aggregation> (capacity, 1.0f); 106 for (Aggregation a : aggregations) { 107 map.put(a.getName(), a); 108 } 109 } 110 111 /** 112 * Gets the nanosecond timestamp of this aggregate snapshot. 113 * 114 * @return nanosecond timestamp of this aggregate snapshot 115 */ 116 public long 117 getSnaptime() 118 { 119 return snaptime; 120 } 121 122 /** 123 * Gets an unordered list of all aggregations in this aggregate 124 * snapshot. The list is easily sortable using {@link 125 * java.util.Collections#sort(List list, Comparator c)} provided any 126 * user-defined ordering. Modifying the returned list has no effect 127 * on this aggregate. Supports XML persistence. 128 * 129 * @return modifiable unordered list of all aggregations in this 130 * aggregate snapshot; list is non-null and possibly empty 131 */ 132 public List <Aggregation> 133 getAggregations() 134 { 135 // Must return an instance of a public, mutable class in order 136 // to support XML persistence. 137 List <Aggregation> list = new ArrayList <Aggregation> (map.size()); 138 list.addAll(map.values()); 139 return list; 140 } 141 142 /** 143 * Gets the aggregation with the given name if it exists in this 144 * aggregate snapshot. 145 * 146 * @param name the name of the desired aggregation, or empty string 147 * to request the unnamed aggregation. In D, the unnamed 148 * aggregation is used anytime a name does not follow the 149 * aggregation symbol '{@code @}', for example: 150 * <pre> {@code @ = count();}</pre> as opposed to 151 * <pre> {@code @counts = count()}</pre> resulting in an 152 * {@code Aggregation} with the name "counts". 153 * 154 * @return {@code null} if no aggregation by the given name exists 155 * in this aggregate 156 * @see Aggregation#getName() 157 */ 158 public Aggregation 159 getAggregation(String name) 160 { 161 // This was decided March 18, 2005 in a meeting with the DTrace 162 // team that calling getAggregation() with underbar should 163 // return the unnamed aggregation (same as calling with empty 164 // string). Underbar is used to identify the unnamed 165 // aggregation in libdtrace; in the jave API it is identifed by 166 // the empty string. The API never presents underbar but 167 // accepts it as input (just converts underbar to empty string 168 // everywhere it sees it). 169 name = Aggregate.filterUnnamedAggregationName(name); 170 return map.get(name); 171 } 172 173 /** 174 * In the native DTrace library, the unnamed aggregation {@code @} 175 * is given the name {@code _} (underbar). The Java DTrace API does 176 * not expose this implementation detail but instead identifies the 177 * unnamed aggregation with the empty string. Here we convert the 178 * name of the unnamed aggregation at the earliest opportunity. 179 * <p> 180 * Package level access. Called by this class and PrintaRecord when 181 * adding the Aggregation abstraction on top of native aggregation 182 * records. 183 */ 184 static String 185 filterUnnamedAggregationName(String name) 186 { 187 if ((name != null) && name.equals("_")) { 188 return ""; 189 } 190 return name; 191 } 192 193 /** 194 * Gets a read-only {@code Map} view of this aggregate. 195 * 196 * @return a read-only {@code Map} view of this aggregate keyed by 197 * aggregation name 198 */ 199 public Map <String, Aggregation> 200 asMap() 201 { 202 return Collections. <String, Aggregation> unmodifiableMap(map); 203 } 204 205 /** 206 * Called by native code. 207 * 208 * @throws IllegalStateException if the aggregation with the given 209 * name already has a record with the same tuple key as the given 210 * record. 211 */ 212 private void 213 addRecord(String aggregationName, long aggid, AggregationRecord rec) 214 { 215 aggregationName = Aggregate.filterUnnamedAggregationName( 216 aggregationName); 217 Aggregation aggregation = getAggregation(aggregationName); 218 if (aggregation == null) { 219 aggregation = new Aggregation(aggregationName, aggid); 220 map.put(aggregationName, aggregation); 221 } 222 aggregation.addRecord(rec); 223 } 224 225 /** 226 * Serialize this {@code Aggregate} instance. 227 * 228 * @serialData Serialized fields are emitted, followed by a {@link 229 * java.util.List} of {@link Aggregation} instances. 230 */ 231 private void 232 writeObject(ObjectOutputStream s) throws IOException 233 { 234 s.defaultWriteObject(); 235 s.writeObject(getAggregations()); 236 } 237 238 @SuppressWarnings("unchecked") 239 private void 240 readObject(ObjectInputStream s) 241 throws IOException, ClassNotFoundException 242 { 243 s.defaultReadObject(); 244 // cannot cast to parametric type without compiler warning 245 List <Aggregation> aggregations = (List)s.readObject(); 246 // load serialized form into private map as a defensive copy 247 mapAggregations(aggregations); 248 // check class invariants after defensive copy 249 } 250 251 /** 252 * Gets a string representation of this aggregate snapshot useful 253 * for logging and not intended for display. The exact details of 254 * the representation are unspecified and subject to change, but the 255 * following format may be regarded as typical: 256 * <pre><code> 257 * class-name[property1 = value1, property2 = value2] 258 * </code></pre> 259 */ 260 public String 261 toString() 262 { 263 StringBuffer buf = new StringBuffer(); 264 buf.append(Aggregate.class.getName()); 265 buf.append("[snaptime = "); 266 buf.append(snaptime); 267 buf.append(", aggregations = "); 268 List <Aggregation> a = getAggregations(); 269 Collections.sort(a, new Comparator <Aggregation> () { 270 public int compare(Aggregation a1, Aggregation a2) { 271 return a1.getName().compareTo(a2.getName()); 272 } 273 }); 274 buf.append(Arrays.toString(a.toArray())); 275 buf.append(']'); 276 return buf.toString(); 277 } 278 } 279