2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A *
2N/A * ident "%Z%%M% %I% %E% SMI"
2N/A */
2N/Apackage org.opensolaris.os.dtrace;
2N/A
2N/Aimport java.util.*;
2N/Aimport java.beans.*;
2N/Aimport java.io.*;
2N/A
2N/A/**
2N/A * A consistent snapshot of all aggregations requested by a single
2N/A * {@link Consumer}.
2N/A * <p>
2N/A * Immutable. Supports persistence using {@link java.beans.XMLEncoder}.
2N/A *
2N/A * @see Consumer#getAggregate()
2N/A *
2N/A * @author Tom Erickson
2N/A */
2N/Apublic final class Aggregate implements Serializable
2N/A{
2N/A static final long serialVersionUID = 3180340417154076628L;
2N/A
2N/A static {
2N/A try {
2N/A BeanInfo info = Introspector.getBeanInfo(Aggregate.class);
2N/A PersistenceDelegate persistenceDelegate =
2N/A new DefaultPersistenceDelegate(
2N/A new String[] {"snaptime", "aggregations"});
2N/A BeanDescriptor d = info.getBeanDescriptor();
2N/A d.setValue("persistenceDelegate", persistenceDelegate);
2N/A } catch (IntrospectionException e) {
2N/A System.out.println(e);
2N/A }
2N/A }
2N/A
2N/A /** @serial */
2N/A private final long snaptime;
2N/A
2N/A // Map must not have same name as named PersistenceDelegate property
2N/A // ("aggregations"), otherwise it gets confused for a bean property
2N/A // and XMLDecoder calls the constructor with a Map instead of the
2N/A // value of the getAggregations() method.
2N/A
2N/A private transient Map <String, Aggregation> map;
2N/A private transient int recordSequence;
2N/A
2N/A /**
2N/A * Called by native code.
2N/A */
2N/A private
2N/A Aggregate(long snaptimeNanos)
2N/A {
2N/A snaptime = snaptimeNanos;
2N/A map = new HashMap <String, Aggregation> ();
2N/A }
2N/A
2N/A /**
2N/A * Creates an aggregate with the given snaptime and aggregations.
2N/A * Supports XML persistence.
2N/A *
2N/A * @param snaptimeNanos nanosecond timestamp when this aggregate was
2N/A * snapped
2N/A * @param aggregations unordered collection of aggregations
2N/A * belonging to this aggregate
2N/A * @throws NullPointerException if the given collection of
2N/A * aggregations is {@code null}
2N/A * @throws IllegalArgumentException if the record ordinals of the
2N/A * given aggregations are invalid
2N/A */
2N/A public
2N/A Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations)
2N/A {
2N/A snaptime = snaptimeNanos;
2N/A mapAggregations(aggregations);
2N/A validate();
2N/A }
2N/A
2N/A // assumes map is not yet created
2N/A private void
2N/A mapAggregations(Collection <Aggregation> aggregations)
2N/A {
2N/A int capacity = (int)(((float)aggregations.size() * 3.0f) / 2.0f);
2N/A // avoid rehashing and optimize lookup; will never be modified
2N/A map = new HashMap <String, Aggregation> (capacity, 1.0f);
2N/A for (Aggregation a : aggregations) {
2N/A map.put(a.getName(), a);
2N/A recordSequence += a.asMap().size();
2N/A }
2N/A }
2N/A
2N/A private void
2N/A validate()
2N/A {
2N/A int capacity = (int)(((float)recordSequence * 3.0f) / 2.0f);
2N/A Set <Integer> ordinals = new HashSet <Integer> (capacity, 1.0f);
2N/A int ordinal, max = 0;
2N/A for (Aggregation a : map.values()) {
2N/A for (AggregationRecord r : a.asMap().values()) {
2N/A // Allow all ordinals to be zero for backward
2N/A // compatibility (allows XML decoding of aggregates that
2N/A // were encoded before the ordinal property was added).
2N/A if (!ordinals.add(ordinal = r.getOrdinal()) && (ordinal > 0)) {
2N/A throw new IllegalArgumentException(
2N/A "duplicate record ordinal: " + ordinal);
2N/A }
2N/A if (ordinal > max) {
2N/A max = ordinal;
2N/A }
2N/A }
2N/A }
2N/A if ((max > 0) && (max != (recordSequence - 1))) {
2N/A throw new IllegalArgumentException(
2N/A "The maximum record ordinal (" + max + ") does not " +
2N/A "equal the number of records (" + recordSequence +
2N/A ") minus one.");
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * Gets the nanosecond timestamp of this aggregate snapshot.
2N/A *
2N/A * @return nanosecond timestamp of this aggregate snapshot
2N/A */
2N/A public long
2N/A getSnaptime()
2N/A {
2N/A return snaptime;
2N/A }
2N/A
2N/A /**
2N/A * Gets an unordered list of all aggregations in this aggregate
2N/A * snapshot. The list is easily sortable using {@link
2N/A * java.util.Collections#sort(List list, Comparator c)} provided any
2N/A * user-defined ordering. Modifying the returned list has no effect
2N/A * on this aggregate. Supports XML persistence.
2N/A *
2N/A * @return modifiable unordered list of all aggregations in this
2N/A * aggregate snapshot; list is non-null and possibly empty
2N/A */
2N/A public List <Aggregation>
2N/A getAggregations()
2N/A {
2N/A // Must return an instance of a public, mutable class in order
2N/A // to support XML persistence.
2N/A List <Aggregation> list = new ArrayList <Aggregation> (map.size());
2N/A list.addAll(map.values());
2N/A return list;
2N/A }
2N/A
2N/A /**
2N/A * Gets the aggregation with the given name if it exists in this
2N/A * aggregate snapshot.
2N/A *
2N/A * @param name the name of the desired aggregation, or empty string
2N/A * to request the unnamed aggregation. In D, the unnamed
2N/A * aggregation is used anytime a name does not follow the
2N/A * aggregation symbol '{@code @}', for example:
2N/A * <pre> {@code @ = count();}</pre> as opposed to
2N/A * <pre> {@code @counts = count()}</pre> resulting in an
2N/A * {@code Aggregation} with the name "counts".
2N/A *
2N/A * @return {@code null} if no aggregation by the given name exists
2N/A * in this aggregate
2N/A * @see Aggregation#getName()
2N/A */
2N/A public Aggregation
2N/A getAggregation(String name)
2N/A {
2N/A // This was decided March 18, 2005 in a meeting with the DTrace
2N/A // team that calling getAggregation() with underbar should
2N/A // return the unnamed aggregation (same as calling with empty
2N/A // string). Underbar is used to identify the unnamed
2N/A // aggregation in libdtrace; in the jave API it is identifed by
2N/A // the empty string. The API never presents underbar but
2N/A // accepts it as input (just converts underbar to empty string
2N/A // everywhere it sees it).
2N/A name = Aggregate.filterUnnamedAggregationName(name);
2N/A return map.get(name);
2N/A }
2N/A
2N/A /**
2N/A * Gets an unordered list of this aggregate's records. The list is
2N/A * sortable using {@link java.util.Collections#sort(List list,
2N/A * Comparator c)} with any user-defined ordering. Modifying the
2N/A * returned list has no effect on this aggregate.
2N/A *
2N/A * @return a newly created list that copies this aggregate's records
2N/A * by reference in no particular order
2N/A */
2N/A public List <AggregationRecord>
2N/A getRecords()
2N/A {
2N/A List <AggregationRecord> list =
2N/A new ArrayList <AggregationRecord> (recordSequence);
2N/A for (Aggregation a : map.values()) {
2N/A list.addAll(a.asMap().values());
2N/A }
2N/A return list;
2N/A }
2N/A
2N/A /**
2N/A * Gets an ordered list of this aggregate's records sequenced by
2N/A * their {@link AggregationRecord#getOrdinal() ordinal} property.
2N/A * Note that the unordered list returned by {@link #getRecords()}
2N/A * can easily be sorted by any arbitrary criteria, for example by
2N/A * key ascending:
2N/A * <pre><code>
2N/A * List <AggregationRecord> records = aggregate.getRecords();
2N/A * Collections.sort(records, new Comparator &lt;AggregationRecord&gt; () {
2N/A * public int compare(AggregationRecord r1, AggregationRecord r2) {
2N/A * return r1.getTuple().compareTo(r2.getTuple());
2N/A * }
2N/A * });
2N/A * </code></pre>
2N/A * Use {@code getOrderedRecords()} instead of {@code getRecords()}
2N/A * when you want to list records as they would be ordered by {@code
2N/A * dtrace(1M)}.
2N/A *
2N/A * @return a newly created list of this aggregate's records
2N/A * in the order used by the native DTrace library
2N/A */
2N/A public List <AggregationRecord>
2N/A getOrderedRecords()
2N/A {
2N/A List <AggregationRecord> list = getRecords();
2N/A Collections.sort(list, new Comparator <AggregationRecord> () {
2N/A public int compare(AggregationRecord r1, AggregationRecord r2) {
2N/A int n1 = r1.getOrdinal();
2N/A int n2 = r2.getOrdinal();
2N/A return (n1 < n2 ? -1 : (n1 > n2 ? 1 : 0));
2N/A }
2N/A });
2N/A return list;
2N/A }
2N/A
2N/A /**
2N/A * In the native DTrace library, the unnamed aggregation {@code @}
2N/A * is given the name {@code _} (underbar). The Java DTrace API does
2N/A * not expose this implementation detail but instead identifies the
2N/A * unnamed aggregation with the empty string. Here we convert the
2N/A * name of the unnamed aggregation at the earliest opportunity.
2N/A * <p>
2N/A * Package level access. Called by this class and PrintaRecord when
2N/A * adding the Aggregation abstraction on top of native aggregation
2N/A * records.
2N/A */
2N/A static String
2N/A filterUnnamedAggregationName(String name)
2N/A {
2N/A if ((name != null) && name.equals("_")) {
2N/A return "";
2N/A }
2N/A return name;
2N/A }
2N/A
2N/A /**
2N/A * Gets a read-only {@code Map} view of this aggregate.
2N/A *
2N/A * @return a read-only {@code Map} view of this aggregate keyed by
2N/A * aggregation name
2N/A */
2N/A public Map <String, Aggregation>
2N/A asMap()
2N/A {
2N/A return Collections. <String, Aggregation> unmodifiableMap(map);
2N/A }
2N/A
2N/A /**
2N/A * Called by native code.
2N/A *
2N/A * @throws IllegalStateException if the aggregation with the given
2N/A * name already has a record with the same tuple key as the given
2N/A * record.
2N/A */
2N/A private void
2N/A addRecord(String aggregationName, long aggid, AggregationRecord rec)
2N/A {
2N/A rec.setOrdinal(recordSequence++);
2N/A aggregationName = Aggregate.filterUnnamedAggregationName(
2N/A aggregationName);
2N/A Aggregation aggregation = getAggregation(aggregationName);
2N/A if (aggregation == null) {
2N/A aggregation = new Aggregation(aggregationName, aggid);
2N/A map.put(aggregationName, aggregation);
2N/A }
2N/A aggregation.addRecord(rec);
2N/A }
2N/A
2N/A /**
2N/A * Serialize this {@code Aggregate} instance.
2N/A *
2N/A * @serialData Serialized fields are emitted, followed by a {@link
2N/A * java.util.List} of {@link Aggregation} instances.
2N/A */
2N/A private void
2N/A writeObject(ObjectOutputStream s) throws IOException
2N/A {
2N/A s.defaultWriteObject();
2N/A s.writeObject(getAggregations());
2N/A }
2N/A
2N/A @SuppressWarnings("unchecked")
2N/A private void
2N/A readObject(ObjectInputStream s)
2N/A throws IOException, ClassNotFoundException
2N/A {
2N/A s.defaultReadObject();
2N/A // cannot cast to parametric type without compiler warning
2N/A List <Aggregation> aggregations = (List)s.readObject();
2N/A // load serialized form into private map as a defensive copy
2N/A mapAggregations(aggregations);
2N/A // check class invariants after defensive copy
2N/A try {
2N/A validate();
2N/A } catch (Exception e) {
2N/A InvalidObjectException x = new InvalidObjectException(
2N/A e.getMessage());
2N/A x.initCause(e);
2N/A throw x;
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * Gets a string representation of this aggregate snapshot useful
2N/A * for logging and not intended for display. The exact details of
2N/A * the representation are unspecified and subject to change, but the
2N/A * following format may be regarded as typical:
2N/A * <pre><code>
2N/A * class-name[property1 = value1, property2 = value2]
2N/A * </code></pre>
2N/A */
2N/A public String
2N/A toString()
2N/A {
2N/A StringBuilder buf = new StringBuilder();
2N/A buf.append(Aggregate.class.getName());
2N/A buf.append("[snaptime = ");
2N/A buf.append(snaptime);
2N/A buf.append(", aggregations = ");
2N/A List <Aggregation> a = getAggregations();
2N/A Collections.sort(a, new Comparator <Aggregation> () {
2N/A public int compare(Aggregation a1, Aggregation a2) {
2N/A return a1.getName().compareTo(a2.getName());
2N/A }
2N/A });
2N/A buf.append(Arrays.toString(a.toArray()));
2N/A buf.append(']');
2N/A return buf.toString();
2N/A }
2N/A}