/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ package org.opensolaris.os.dtrace; import java.util.*; import java.beans.*; import java.io.*; /** * A snapshot of a DTrace aggregation. The name of an {@code * Aggregation} instance matches the source declaration, for example *
        {@code @a[execname] = count();}
* results in an {@code Aggregation} named "a" (the name does not * include the preceding {@code @}). For convenience, a single * aggregation can remain unnamed (multiple aggregations in the same D * program need distinct names). The unnamed aggregation results in an * {@code Aggregation} instance whose name is the empty string, for * example *
        {@code @[execname] = count();}
* An aggregation can list more than one variable in square brackets in * order to accumulate a value for each unique combination, or {@link * Tuple}. Each tuple instance is associated with its accumulated * {@link AggregationValue} in an {@link AggregationRecord}. For * example *
        {@code @counts[execname, probefunc, cpu] = count();}
* results in an {@code Aggregation} named "counts" containing records * each pairing a {@link CountValue} to a three-element {@code Tuple}. * It is also possible to omit the square brackets, for example *
        {@code @a = count();}
* results in an {@code Aggregation} named "a" with only a single record * keyed to the empty tuple ({@link Tuple#EMPTY}). *

* For more information, see the * Aggregations chapter of the Dynamic Tracing * Guide. Also, the * Built-in Variables section of the Variables chapter * describes variables like {@code execname}, {@code probefunc}, and * {@code cpu} useful for aggregating. *

* Immutable. Supports persistence using {@link java.beans.XMLEncoder}. * * @see Aggregate * @see PrintaRecord * * @author Tom Erickson */ public final class Aggregation implements Serializable { static final long serialVersionUID = 2340811719178724026L; static { try { BeanInfo info = Introspector.getBeanInfo(Aggregation.class); PersistenceDelegate persistenceDelegate = new DefaultPersistenceDelegate( new String[] {"name", "ID", "records"}) { @Override protected boolean mutatesTo(Object oldInstance, Object newInstance) { return ((newInstance != null) && (oldInstance != null) && (oldInstance.getClass() == newInstance.getClass())); } }; BeanDescriptor d = info.getBeanDescriptor(); d.setValue("persistenceDelegate", persistenceDelegate); } catch (IntrospectionException e) { e.printStackTrace(); } } /** @serial */ private String name; /** @serial */ private final long id; private transient Map map; /** * Package-level access, called by Aggregate */ Aggregation(String aggregationName, long aggregationID) { name = Aggregate.filterUnnamedAggregationName(aggregationName); id = aggregationID; map = new HashMap (); } /** * Creates an aggregation with the given name, ID, and records. * Supports XML persistence. * * @param aggregationName the name of this aggregation, empty string * if this aggregation is unnamed * @param aggregationID ID generated from a sequence by the native * DTrace library * @param aggregationRecords unordered collection of records * belonging to this aggregation * @throws NullPointerException if the specified name or list of * records is {@code null} * @throws IllegalArgumentException if any record has an empty * tuple, unless it is the only record in the given collection (only * a singleton generated by an aggregation without square brackets * uses {@link Tuple#EMPTY} as a key) * @see #getRecord(Tuple key) */ public Aggregation(String aggregationName, long aggregationID, Collection aggregationRecords) { name = Aggregate.filterUnnamedAggregationName(aggregationName); id = aggregationID; mapRecords(aggregationRecords); validate(); } // assumes map is not yet created private void mapRecords(Collection records) { int capacity = (int)(((float)records.size() * 3.0f) / 2.0f); // avoid rehashing and optimize lookup; will never be modified map = new HashMap (capacity, 1.0f); for (AggregationRecord record : records) { map.put(record.getTuple(), record); } } private final void validate() { if (name == null) { throw new NullPointerException("name is null"); } for (AggregationRecord r : map.values()) { if ((r.getTuple().size() == 0) && (map.size() > 1)) { throw new IllegalArgumentException("empty tuple " + "allowed only in singleton aggregation"); } } } /** * Gets the name of this aggregation. * * @return the name of this aggregation exactly as it appears in the * D program minus the preceding {@code @}, or an empty string if * the aggregation is unnamed, for example: *

		{@code @[execname] = count();}
*/ public String getName() { return name; } /** * Gets the D compiler-generated ID of this aggregation. * * @return the D compiler-generated ID */ public long getID() { return id; } /** * Gets an unordered list of this aggregation's records. The list is * sortable using {@link java.util.Collections#sort(List list, * Comparator c)} with any user-defined ordering. Modifying the * returned list has no effect on this aggregation. Supports XML * persistence. * * @return a newly created list that copies this aggregation's * records by reference in no particular order * @see Aggregate#getRecords() * @see Aggregate#getOrderedRecords() */ public List getRecords() { List list = new ArrayList (map.values()); return list; } /** * Package level access, called by Aggregate and PrintaRecord. * * @throws IllegalArgumentException if this aggregation already * contains a record with the same tuple key as the given record */ void addRecord(AggregationRecord record) { Tuple key = record.getTuple(); if (map.put(key, record) != null) { throw new IllegalArgumentException("already contains a record " + "with tuple " + key); } } /** * Gets a read-only {@code Map} view of this aggregation. * * @return a read-only {@code Map} view of this aggregation */ public Map asMap() { return Collections. unmodifiableMap(map); } /** * Compares the specified object with this aggregation for equality. * Defines equality as having equal names and equal records. * * @return {@code true} if and only if the specified object is an * {@code Aggregation} with the same name as this aggregation and * the {@code Map} views of both aggregations returned by {@link * #asMap()} are equal as defined by {@link * AbstractMap#equals(Object o) AbstractMap.equals()} */ @Override public boolean equals(Object o) { if (o instanceof Aggregation) { Aggregation a = (Aggregation)o; return (name.equals(a.name) && (map.equals(a.map))); // same mappings } return false; } /** * Overridden to ensure that equal aggregations have equal hash * codes. */ @Override public int hashCode() { int hash = 17; hash = (37 * hash) + name.hashCode(); hash = (37 * hash) + map.hashCode(); return hash; } /** * Gets the record associated with the given key, or the singleton * record of an aggregation declared without square brackets if * {@code key} is {@code null} or empty. * * @param key The record key, or an empty tuple (see {@link * Tuple#EMPTY}) to obtain the value from a singleton (a * non-keyed instance with only a single value) generated from a * DTrace aggregation declared without square brackets, for * example: *
		{@code @a = count();}
* @return the record associated with the given key, or {@code null} * if no record in this aggregation is associated with the given key */ public AggregationRecord getRecord(Tuple key) { if (key == null) { key = Tuple.EMPTY; } return map.get(key); } /** * Serialize this {@code Aggregation} instance. * * @serialData Serialized fields are emitted, followed by a {@link * java.util.List} of {@link AggregationRecord} instances. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject(getRecords()); } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // cannot cast to parametric type without compiler warning List records = (List)s.readObject(); // load serialized form into private map as a defensive copy mapRecords(records); // Check class invariants (only after defensive copy) name = Aggregate.filterUnnamedAggregationName(name); try { validate(); } catch (Exception e) { InvalidObjectException x = new InvalidObjectException( e.getMessage()); x.initCause(e); throw x; } } /** * Gets a string representation of this aggregation useful for * logging and not intended for display. The exact details of the * representation are unspecified and subject to change, but the * following format may be regarded as typical: *

     * class-name[property1 = value1, property2 = value2]
     * 
*/ @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(Aggregation.class.getName()); buf.append("[name = "); buf.append(name); buf.append(", id = "); buf.append(id); buf.append(", records = "); List recordList = getRecords(); // Sort by tuple so that equal aggregations have equal strings Collections.sort(recordList, new Comparator () { public int compare(AggregationRecord r1, AggregationRecord r2) { Tuple t1 = r1.getTuple(); Tuple t2 = r2.getTuple(); return t1.compareTo(t2); } }); buf.append('['); boolean first = true; for (AggregationRecord record : recordList) { if (first) { first = false; } else { buf.append(", "); } buf.append(record); } buf.append(']'); return buf.toString(); } }