1fb3fb4f3Stomee /* 2fb3fb4f3Stomee * CDDL HEADER START 3fb3fb4f3Stomee * 4fb3fb4f3Stomee * The contents of this file are subject to the terms of the 5fb3fb4f3Stomee * Common Development and Distribution License (the "License"). 6fb3fb4f3Stomee * You may not use this file except in compliance with the License. 7fb3fb4f3Stomee * 8fb3fb4f3Stomee * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9fb3fb4f3Stomee * or http://www.opensolaris.org/os/licensing. 10fb3fb4f3Stomee * See the License for the specific language governing permissions 11fb3fb4f3Stomee * and limitations under the License. 12fb3fb4f3Stomee * 13fb3fb4f3Stomee * When distributing Covered Code, include this CDDL HEADER in each 14fb3fb4f3Stomee * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15fb3fb4f3Stomee * If applicable, add the following below this CDDL HEADER, with the 16fb3fb4f3Stomee * fields enclosed by brackets "[]" replaced with your own identifying 17fb3fb4f3Stomee * information: Portions Copyright [yyyy] [name of copyright owner] 18fb3fb4f3Stomee * 19fb3fb4f3Stomee * CDDL HEADER END 20fb3fb4f3Stomee */ 21fb3fb4f3Stomee 22fb3fb4f3Stomee /* 23e77b06d2Stomee * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24fb3fb4f3Stomee * Use is subject to license terms. 25fb3fb4f3Stomee */ 26fb3fb4f3Stomee package org.opensolaris.os.dtrace; 27fb3fb4f3Stomee 28fb3fb4f3Stomee import java.util.*; 29fb3fb4f3Stomee import java.beans.*; 30fb3fb4f3Stomee import java.io.*; 31fb3fb4f3Stomee 32fb3fb4f3Stomee /** 33fb3fb4f3Stomee * A consistent snapshot of all aggregations requested by a single 34fb3fb4f3Stomee * {@link Consumer}. 35fb3fb4f3Stomee * <p> 36fb3fb4f3Stomee * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 37fb3fb4f3Stomee * 38fb3fb4f3Stomee * @see Consumer#getAggregate() 39fb3fb4f3Stomee * 40fb3fb4f3Stomee * @author Tom Erickson 41fb3fb4f3Stomee */ 42fb3fb4f3Stomee public final class Aggregate implements Serializable 43fb3fb4f3Stomee { 44fb3fb4f3Stomee static final long serialVersionUID = 3180340417154076628L; 45fb3fb4f3Stomee 46fb3fb4f3Stomee static { 47fb3fb4f3Stomee try { 48fb3fb4f3Stomee BeanInfo info = Introspector.getBeanInfo(Aggregate.class); 49fb3fb4f3Stomee PersistenceDelegate persistenceDelegate = 50fb3fb4f3Stomee new DefaultPersistenceDelegate( 51fb3fb4f3Stomee new String[] {"snaptime", "aggregations"}); 52fb3fb4f3Stomee BeanDescriptor d = info.getBeanDescriptor(); 53fb3fb4f3Stomee d.setValue("persistenceDelegate", persistenceDelegate); 54fb3fb4f3Stomee } catch (IntrospectionException e) { 55fb3fb4f3Stomee System.out.println(e); 56fb3fb4f3Stomee } 57fb3fb4f3Stomee } 58fb3fb4f3Stomee 59fb3fb4f3Stomee /** @serial */ 60fb3fb4f3Stomee private final long snaptime; 61fb3fb4f3Stomee 62fb3fb4f3Stomee // Map must not have same name as named PersistenceDelegate property 63fb3fb4f3Stomee // ("aggregations"), otherwise it gets confused for a bean property 64fb3fb4f3Stomee // and XMLDecoder calls the constructor with a Map instead of the 65fb3fb4f3Stomee // value of the getAggregations() method. 66fb3fb4f3Stomee 67fb3fb4f3Stomee private transient Map <String, Aggregation> map; 68e77b06d2Stomee private transient int recordSequence; 69fb3fb4f3Stomee 70fb3fb4f3Stomee /** 71fb3fb4f3Stomee * Called by native code. 72fb3fb4f3Stomee */ 73fb3fb4f3Stomee private Aggregate(long snaptimeNanos)74fb3fb4f3Stomee Aggregate(long snaptimeNanos) 75fb3fb4f3Stomee { 76fb3fb4f3Stomee snaptime = snaptimeNanos; 77fb3fb4f3Stomee map = new HashMap <String, Aggregation> (); 78fb3fb4f3Stomee } 79fb3fb4f3Stomee 80fb3fb4f3Stomee /** 81fb3fb4f3Stomee * Creates an aggregate with the given snaptime and aggregations. 82fb3fb4f3Stomee * Supports XML persistence. 83fb3fb4f3Stomee * 84fb3fb4f3Stomee * @param snaptimeNanos nanosecond timestamp when this aggregate was 85fb3fb4f3Stomee * snapped 86fb3fb4f3Stomee * @param aggregations unordered collection of aggregations 87fb3fb4f3Stomee * belonging to this aggregate 88fb3fb4f3Stomee * @throws NullPointerException if the given collection of 89fb3fb4f3Stomee * aggregations is {@code null} 90e77b06d2Stomee * @throws IllegalArgumentException if the record ordinals of the 91e77b06d2Stomee * given aggregations are invalid 92fb3fb4f3Stomee */ 93fb3fb4f3Stomee public Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations)94fb3fb4f3Stomee Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations) 95fb3fb4f3Stomee { 96fb3fb4f3Stomee snaptime = snaptimeNanos; 97fb3fb4f3Stomee mapAggregations(aggregations); 98e77b06d2Stomee validate(); 99fb3fb4f3Stomee } 100fb3fb4f3Stomee 101fb3fb4f3Stomee // assumes map is not yet created 102fb3fb4f3Stomee private void mapAggregations(Collection <Aggregation> aggregations)103fb3fb4f3Stomee mapAggregations(Collection <Aggregation> aggregations) 104fb3fb4f3Stomee { 105fb3fb4f3Stomee int capacity = (int)(((float)aggregations.size() * 3.0f) / 2.0f); 106fb3fb4f3Stomee // avoid rehashing and optimize lookup; will never be modified 107fb3fb4f3Stomee map = new HashMap <String, Aggregation> (capacity, 1.0f); 108fb3fb4f3Stomee for (Aggregation a : aggregations) { 109fb3fb4f3Stomee map.put(a.getName(), a); 110e77b06d2Stomee recordSequence += a.asMap().size(); 111e77b06d2Stomee } 112e77b06d2Stomee } 113e77b06d2Stomee 114e77b06d2Stomee private void validate()115e77b06d2Stomee validate() 116e77b06d2Stomee { 117e77b06d2Stomee int capacity = (int)(((float)recordSequence * 3.0f) / 2.0f); 118e77b06d2Stomee Set <Integer> ordinals = new HashSet <Integer> (capacity, 1.0f); 119e77b06d2Stomee int ordinal, max = 0; 120e77b06d2Stomee for (Aggregation a : map.values()) { 121e77b06d2Stomee for (AggregationRecord r : a.asMap().values()) { 122e77b06d2Stomee // Allow all ordinals to be zero for backward 123e77b06d2Stomee // compatibility (allows XML decoding of aggregates that 124e77b06d2Stomee // were encoded before the ordinal property was added). 125e77b06d2Stomee if (!ordinals.add(ordinal = r.getOrdinal()) && (ordinal > 0)) { 126e77b06d2Stomee throw new IllegalArgumentException( 127e77b06d2Stomee "duplicate record ordinal: " + ordinal); 128e77b06d2Stomee } 129e77b06d2Stomee if (ordinal > max) { 130e77b06d2Stomee max = ordinal; 131e77b06d2Stomee } 132e77b06d2Stomee } 133e77b06d2Stomee } 134e77b06d2Stomee if ((max > 0) && (max != (recordSequence - 1))) { 135e77b06d2Stomee throw new IllegalArgumentException( 136e77b06d2Stomee "The maximum record ordinal (" + max + ") does not " + 137e77b06d2Stomee "equal the number of records (" + recordSequence + 138e77b06d2Stomee ") minus one."); 139fb3fb4f3Stomee } 140fb3fb4f3Stomee } 141fb3fb4f3Stomee 142fb3fb4f3Stomee /** 143fb3fb4f3Stomee * Gets the nanosecond timestamp of this aggregate snapshot. 144fb3fb4f3Stomee * 145fb3fb4f3Stomee * @return nanosecond timestamp of this aggregate snapshot 146fb3fb4f3Stomee */ 147fb3fb4f3Stomee public long getSnaptime()148fb3fb4f3Stomee getSnaptime() 149fb3fb4f3Stomee { 150fb3fb4f3Stomee return snaptime; 151fb3fb4f3Stomee } 152fb3fb4f3Stomee 153fb3fb4f3Stomee /** 154fb3fb4f3Stomee * Gets an unordered list of all aggregations in this aggregate 155fb3fb4f3Stomee * snapshot. The list is easily sortable using {@link 156fb3fb4f3Stomee * java.util.Collections#sort(List list, Comparator c)} provided any 157fb3fb4f3Stomee * user-defined ordering. Modifying the returned list has no effect 158fb3fb4f3Stomee * on this aggregate. Supports XML persistence. 159fb3fb4f3Stomee * 160fb3fb4f3Stomee * @return modifiable unordered list of all aggregations in this 161fb3fb4f3Stomee * aggregate snapshot; list is non-null and possibly empty 162fb3fb4f3Stomee */ 163fb3fb4f3Stomee public List <Aggregation> getAggregations()164fb3fb4f3Stomee getAggregations() 165fb3fb4f3Stomee { 166fb3fb4f3Stomee // Must return an instance of a public, mutable class in order 167fb3fb4f3Stomee // to support XML persistence. 168fb3fb4f3Stomee List <Aggregation> list = new ArrayList <Aggregation> (map.size()); 169fb3fb4f3Stomee list.addAll(map.values()); 170fb3fb4f3Stomee return list; 171fb3fb4f3Stomee } 172fb3fb4f3Stomee 173fb3fb4f3Stomee /** 174fb3fb4f3Stomee * Gets the aggregation with the given name if it exists in this 175fb3fb4f3Stomee * aggregate snapshot. 176fb3fb4f3Stomee * 177fb3fb4f3Stomee * @param name the name of the desired aggregation, or empty string 178fb3fb4f3Stomee * to request the unnamed aggregation. In D, the unnamed 179fb3fb4f3Stomee * aggregation is used anytime a name does not follow the 180fb3fb4f3Stomee * aggregation symbol '{@code @}', for example: 181fb3fb4f3Stomee * <pre> {@code @ = count();}</pre> as opposed to 182fb3fb4f3Stomee * <pre> {@code @counts = count()}</pre> resulting in an 183fb3fb4f3Stomee * {@code Aggregation} with the name "counts". 184fb3fb4f3Stomee * 185fb3fb4f3Stomee * @return {@code null} if no aggregation by the given name exists 186fb3fb4f3Stomee * in this aggregate 187fb3fb4f3Stomee * @see Aggregation#getName() 188fb3fb4f3Stomee */ 189fb3fb4f3Stomee public Aggregation getAggregation(String name)190fb3fb4f3Stomee getAggregation(String name) 191fb3fb4f3Stomee { 192fb3fb4f3Stomee // This was decided March 18, 2005 in a meeting with the DTrace 193fb3fb4f3Stomee // team that calling getAggregation() with underbar should 194fb3fb4f3Stomee // return the unnamed aggregation (same as calling with empty 195fb3fb4f3Stomee // string). Underbar is used to identify the unnamed 1963a931819SPeter Tribble // aggregation in libdtrace; in the java API it is identified by 197fb3fb4f3Stomee // the empty string. The API never presents underbar but 198fb3fb4f3Stomee // accepts it as input (just converts underbar to empty string 199fb3fb4f3Stomee // everywhere it sees it). 200fb3fb4f3Stomee name = Aggregate.filterUnnamedAggregationName(name); 201fb3fb4f3Stomee return map.get(name); 202fb3fb4f3Stomee } 203fb3fb4f3Stomee 204e77b06d2Stomee /** 205e77b06d2Stomee * Gets an unordered list of this aggregate's records. The list is 206e77b06d2Stomee * sortable using {@link java.util.Collections#sort(List list, 207e77b06d2Stomee * Comparator c)} with any user-defined ordering. Modifying the 208e77b06d2Stomee * returned list has no effect on this aggregate. 209e77b06d2Stomee * 210e77b06d2Stomee * @return a newly created list that copies this aggregate's records 211e77b06d2Stomee * by reference in no particular order 212e77b06d2Stomee */ 213e77b06d2Stomee public List <AggregationRecord> getRecords()214e77b06d2Stomee getRecords() 215e77b06d2Stomee { 216e77b06d2Stomee List <AggregationRecord> list = 217e77b06d2Stomee new ArrayList <AggregationRecord> (recordSequence); 218e77b06d2Stomee for (Aggregation a : map.values()) { 219e77b06d2Stomee list.addAll(a.asMap().values()); 220e77b06d2Stomee } 221e77b06d2Stomee return list; 222e77b06d2Stomee } 223e77b06d2Stomee 224e77b06d2Stomee /** 225e77b06d2Stomee * Gets an ordered list of this aggregate's records sequenced by 226e77b06d2Stomee * their {@link AggregationRecord#getOrdinal() ordinal} property. 227e77b06d2Stomee * Note that the unordered list returned by {@link #getRecords()} 228e77b06d2Stomee * can easily be sorted by any arbitrary criteria, for example by 229e77b06d2Stomee * key ascending: 230e77b06d2Stomee * <pre><code> 2313a931819SPeter Tribble * List <AggregationRecord> records = aggregate.getRecords(); 232e77b06d2Stomee * Collections.sort(records, new Comparator <AggregationRecord> () { 2333a931819SPeter Tribble * public int compare(AggregationRecord r1, AggregationRecord r2) { 2343a931819SPeter Tribble * return r1.getTuple().compareTo(r2.getTuple()); 2353a931819SPeter Tribble * } 236e77b06d2Stomee * }); 237e77b06d2Stomee * </code></pre> 238e77b06d2Stomee * Use {@code getOrderedRecords()} instead of {@code getRecords()} 239e77b06d2Stomee * when you want to list records as they would be ordered by {@code 240*bbf21555SRichard Lowe * dtrace(8)}. 241e77b06d2Stomee * 242e77b06d2Stomee * @return a newly created list of this aggregate's records 243e77b06d2Stomee * in the order used by the native DTrace library 244e77b06d2Stomee */ 245e77b06d2Stomee public List <AggregationRecord> getOrderedRecords()246e77b06d2Stomee getOrderedRecords() 247e77b06d2Stomee { 248e77b06d2Stomee List <AggregationRecord> list = getRecords(); 249e77b06d2Stomee Collections.sort(list, new Comparator <AggregationRecord> () { 250e77b06d2Stomee public int compare(AggregationRecord r1, AggregationRecord r2) { 251e77b06d2Stomee int n1 = r1.getOrdinal(); 252e77b06d2Stomee int n2 = r2.getOrdinal(); 253e77b06d2Stomee return (n1 < n2 ? -1 : (n1 > n2 ? 1 : 0)); 254e77b06d2Stomee } 255e77b06d2Stomee }); 256e77b06d2Stomee return list; 257e77b06d2Stomee } 258e77b06d2Stomee 259fb3fb4f3Stomee /** 260fb3fb4f3Stomee * In the native DTrace library, the unnamed aggregation {@code @} 261fb3fb4f3Stomee * is given the name {@code _} (underbar). The Java DTrace API does 262fb3fb4f3Stomee * not expose this implementation detail but instead identifies the 263fb3fb4f3Stomee * unnamed aggregation with the empty string. Here we convert the 264fb3fb4f3Stomee * name of the unnamed aggregation at the earliest opportunity. 265fb3fb4f3Stomee * <p> 266fb3fb4f3Stomee * Package level access. Called by this class and PrintaRecord when 267fb3fb4f3Stomee * adding the Aggregation abstraction on top of native aggregation 268fb3fb4f3Stomee * records. 269fb3fb4f3Stomee */ 270fb3fb4f3Stomee static String filterUnnamedAggregationName(String name)271fb3fb4f3Stomee filterUnnamedAggregationName(String name) 272fb3fb4f3Stomee { 273fb3fb4f3Stomee if ((name != null) && name.equals("_")) { 274fb3fb4f3Stomee return ""; 275fb3fb4f3Stomee } 276fb3fb4f3Stomee return name; 277fb3fb4f3Stomee } 278fb3fb4f3Stomee 279fb3fb4f3Stomee /** 280fb3fb4f3Stomee * Gets a read-only {@code Map} view of this aggregate. 281fb3fb4f3Stomee * 282fb3fb4f3Stomee * @return a read-only {@code Map} view of this aggregate keyed by 283fb3fb4f3Stomee * aggregation name 284fb3fb4f3Stomee */ 285fb3fb4f3Stomee public Map <String, Aggregation> asMap()286fb3fb4f3Stomee asMap() 287fb3fb4f3Stomee { 288fb3fb4f3Stomee return Collections. <String, Aggregation> unmodifiableMap(map); 289fb3fb4f3Stomee } 290fb3fb4f3Stomee 291fb3fb4f3Stomee /** 292fb3fb4f3Stomee * Called by native code. 293fb3fb4f3Stomee * 294fb3fb4f3Stomee * @throws IllegalStateException if the aggregation with the given 295fb3fb4f3Stomee * name already has a record with the same tuple key as the given 296fb3fb4f3Stomee * record. 297fb3fb4f3Stomee */ 298fb3fb4f3Stomee private void addRecord(String aggregationName, long aggid, AggregationRecord rec)299fb3fb4f3Stomee addRecord(String aggregationName, long aggid, AggregationRecord rec) 300fb3fb4f3Stomee { 301e77b06d2Stomee rec.setOrdinal(recordSequence++); 302fb3fb4f3Stomee aggregationName = Aggregate.filterUnnamedAggregationName( 303fb3fb4f3Stomee aggregationName); 304fb3fb4f3Stomee Aggregation aggregation = getAggregation(aggregationName); 305fb3fb4f3Stomee if (aggregation == null) { 306fb3fb4f3Stomee aggregation = new Aggregation(aggregationName, aggid); 307fb3fb4f3Stomee map.put(aggregationName, aggregation); 308fb3fb4f3Stomee } 309fb3fb4f3Stomee aggregation.addRecord(rec); 310fb3fb4f3Stomee } 311fb3fb4f3Stomee 312fb3fb4f3Stomee /** 313fb3fb4f3Stomee * Serialize this {@code Aggregate} instance. 314fb3fb4f3Stomee * 315fb3fb4f3Stomee * @serialData Serialized fields are emitted, followed by a {@link 316fb3fb4f3Stomee * java.util.List} of {@link Aggregation} instances. 317fb3fb4f3Stomee */ 318fb3fb4f3Stomee private void writeObject(ObjectOutputStream s)319fb3fb4f3Stomee writeObject(ObjectOutputStream s) throws IOException 320fb3fb4f3Stomee { 321fb3fb4f3Stomee s.defaultWriteObject(); 322fb3fb4f3Stomee s.writeObject(getAggregations()); 323fb3fb4f3Stomee } 324fb3fb4f3Stomee 325fb3fb4f3Stomee @SuppressWarnings("unchecked") 326fb3fb4f3Stomee private void readObject(ObjectInputStream s)327fb3fb4f3Stomee readObject(ObjectInputStream s) 328fb3fb4f3Stomee throws IOException, ClassNotFoundException 329fb3fb4f3Stomee { 330fb3fb4f3Stomee s.defaultReadObject(); 331fb3fb4f3Stomee // cannot cast to parametric type without compiler warning 332fb3fb4f3Stomee List <Aggregation> aggregations = (List)s.readObject(); 333fb3fb4f3Stomee // load serialized form into private map as a defensive copy 334fb3fb4f3Stomee mapAggregations(aggregations); 335fb3fb4f3Stomee // check class invariants after defensive copy 336e77b06d2Stomee try { 337e77b06d2Stomee validate(); 338e77b06d2Stomee } catch (Exception e) { 339e77b06d2Stomee InvalidObjectException x = new InvalidObjectException( 340e77b06d2Stomee e.getMessage()); 341e77b06d2Stomee x.initCause(e); 342e77b06d2Stomee throw x; 343e77b06d2Stomee } 344fb3fb4f3Stomee } 345fb3fb4f3Stomee 346fb3fb4f3Stomee /** 347fb3fb4f3Stomee * Gets a string representation of this aggregate snapshot useful 348fb3fb4f3Stomee * for logging and not intended for display. The exact details of 349fb3fb4f3Stomee * the representation are unspecified and subject to change, but the 350fb3fb4f3Stomee * following format may be regarded as typical: 351fb3fb4f3Stomee * <pre><code> 352fb3fb4f3Stomee * class-name[property1 = value1, property2 = value2] 353fb3fb4f3Stomee * </code></pre> 354fb3fb4f3Stomee */ 355fb3fb4f3Stomee public String toString()356fb3fb4f3Stomee toString() 357fb3fb4f3Stomee { 3584ae67516Stomee StringBuilder buf = new StringBuilder(); 359fb3fb4f3Stomee buf.append(Aggregate.class.getName()); 360fb3fb4f3Stomee buf.append("[snaptime = "); 361fb3fb4f3Stomee buf.append(snaptime); 362fb3fb4f3Stomee buf.append(", aggregations = "); 363fb3fb4f3Stomee List <Aggregation> a = getAggregations(); 364fb3fb4f3Stomee Collections.sort(a, new Comparator <Aggregation> () { 365fb3fb4f3Stomee public int compare(Aggregation a1, Aggregation a2) { 366fb3fb4f3Stomee return a1.getName().compareTo(a2.getName()); 367fb3fb4f3Stomee } 368fb3fb4f3Stomee }); 369fb3fb4f3Stomee buf.append(Arrays.toString(a.toArray())); 370fb3fb4f3Stomee buf.append(']'); 371fb3fb4f3Stomee return buf.toString(); 372fb3fb4f3Stomee } 373fb3fb4f3Stomee } 374