001/*
002 * JPPF.
003 * Copyright (C) 2005-2016 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jppf.utils.stats;
019
020import java.io.*;
021import java.util.*;
022import java.util.concurrent.*;
023
024/**
025 * Instances of this class hold statistics snapshots.
026 * To create and add a new snapshot, <code>createSnapshot(String)</code> should be called first,
027 * before any call to one of the <code>addXXX()</code> methods, in order to respect thread safety.
028 * @author Laurent Cohen
029 */
030public class JPPFStatistics implements Serializable, Iterable<JPPFSnapshot> {
031  /**
032   * Explicit serialVersionUID.
033   */
034  private static final long serialVersionUID = 1L;
035  /**
036   * A filter which accepts all snapshots.
037   */
038  public static final Filter NOOP_FILTER = new Filter() {
039    @Override
040    public boolean accept(final JPPFSnapshot snapshot) {
041      return true;
042    }
043  };
044  /**
045   * Contains all snapshots currently handled.
046   */
047  private final ConcurrentHashMap<String, JPPFSnapshot> snapshots = new ConcurrentHashMap<>();
048  /**
049   * The list of listeners.
050   */
051  private transient List<ListenerInfo> listeners = new CopyOnWriteArrayList<>();
052
053  /**
054   * Default constructor.
055   * @exclude
056   */
057  public JPPFStatistics() {
058  }
059
060  /**
061   * Copy constructor.
062   * @param map the statistics map to copy.
063   * @exclude
064   */
065  private JPPFStatistics(final Map<String, JPPFSnapshot> map) {
066    for (Map.Entry<String, JPPFSnapshot> entry: map.entrySet()) snapshots.put(entry.getKey(), entry.getValue().copy());
067  }
068
069  /**
070   * Get a snapshot specified by its label.
071   * @param label the label of the snapshot to look up.
072   * @return a {@link JPPFSnapshot} instance, or <code>null</code> if none could be found with the specified label.
073   */
074  public JPPFSnapshot getSnapshot(final String label) {
075    return snapshots.get(label);
076  }
077
078  /**
079   * Create a snapshot with the specified label if it doesn't exist.
080   * If a snapshot with this label already exists, it is returned.
081   * @param label the label of the snapshot to create.
082   * @return a {@link JPPFSnapshot} instance representing the newly created snapshot or the exsting one.
083   * @exclude
084   */
085  public JPPFSnapshot createSnapshot(final String label) {
086    return createSnapshot(false, label);
087  }
088
089  /**
090   * Create a snapshot with the specified label if it doesn't exist.
091   * If a snapshot with this label already exists, it is returned.
092   * @param label the label of the snapshot to create.
093   * @param cumulative determines whether updates are accumulated instead of simply stored as latest value.
094   * @return a {@link JPPFSnapshot} instance representing the newly created snapshot or the exsting one.
095   * @exclude
096   */
097  public JPPFSnapshot createSnapshot(final boolean cumulative, final String label) {
098    JPPFSnapshot newSnapshot = cumulative ? new CumulativeSnapshot(label) : new NonCumulativeSnapshot(label);
099    JPPFSnapshot oldSnapshot = snapshots.putIfAbsent(label, newSnapshot);
100    JPPFSnapshot snapshot = oldSnapshot == null ? newSnapshot : oldSnapshot;
101    if (!listeners.isEmpty()) fireEvent(snapshot, EventType.ADDED);
102    return snapshot;
103  }
104
105  /**
106   * Create a single value snapshot with the specified label if it doesn't exist.
107   * If a snapshot with this label already exists, it is returned.
108   * @param label the label of the snapshot to create.
109   * @return a {@link JPPFSnapshot} instance representing the newly created snapshot or the exsting one.
110   * @exclude
111   */
112  public JPPFSnapshot createSingleValueSnapshot(final String label) {
113    JPPFSnapshot newSnapshot = new SingleValueSnapshot(label);
114    JPPFSnapshot oldSnapshot = snapshots.putIfAbsent(label, newSnapshot);
115    JPPFSnapshot snapshot = oldSnapshot == null ? newSnapshot : oldSnapshot;
116    if (!listeners.isEmpty()) fireEvent(snapshot, EventType.ADDED);
117    return snapshot;
118  }
119
120  /**
121   * Create an array of snapshots with the specified labels, if it doesn't exist.
122   * If one of the snapshots already exists, it is returned.
123   * @param labels the label of the snapshot to create.
124   * @return an array of {@link JPPFSnapshot} instances representing the newly created or exsting snapshots, in the same order as the input labels.
125   * @exclude
126   */
127  public JPPFSnapshot[] createSnapshots(final String...labels) {
128    return createSnapshots(false, labels);
129  }
130
131  /**
132   * Create an array of snapshots with the specified labels, if they don't exist.
133   * If any of the snapshots already exists, it is returned.
134   * @param labels the label of the snapshot to create.
135   * @param cumulative determines whether updates are accumulated instead of simply stored as latest value.
136   * @return an array of {@link JPPFSnapshot} instances representing the newly created or exsting snapshots, in the same order as the input labels.
137   * @exclude
138   */
139  public JPPFSnapshot[] createSnapshots(final boolean cumulative, final String...labels) {
140    JPPFSnapshot[] snapshots = new JPPFSnapshot[labels.length];
141    for (int i=0; i<labels.length; i++) snapshots[i] = createSnapshot(cumulative, labels[i]);
142    return snapshots;
143  }
144
145  /**
146   * Create an array of single value snapshots with the specified labels, if they don't exist.
147   * If any of the snapshots already exists, it is returned.
148   * @param labels the label of the snapshot to create.
149   * @return an array of {@link JPPFSnapshot} instances representing the newly created or exsting snapshots, in the same order as the input labels.
150   * @exclude
151   */
152  public JPPFSnapshot[] createSingleValueSnapshots(final String...labels) {
153    JPPFSnapshot[] snapshots = new JPPFSnapshot[labels.length];
154    for (int i=0; i<labels.length; i++) snapshots[i] = createSingleValueSnapshot(labels[i]);
155    return snapshots;
156  }
157
158  /**
159   * Remove the snapshot with the specified label.
160   * If a snapshot with this label already exists, it is returned.
161   * @param label the label of the snapshot to create.
162   * @return a {@link JPPFSnapshot} instance representing the removed snapshot or <code>null</code> if the label is not in the map.
163   * @exclude
164   */
165  public JPPFSnapshot removeSnapshot(final String label) {
166    JPPFSnapshot snapshot = snapshots.remove(label);
167    if (!listeners.isEmpty()) fireEvent(snapshot, EventType.REMOVED);
168    return snapshot;
169  }
170
171  /**
172   * Add the specified value to the snapshot with the specified label.
173   * @param label the label of the snapshot to add the value to.
174   * @param value the accumulated sum of the values to add.
175   * @return a reference to the updated {@link JPPFSnapshot} object.
176   * @throws IllegalStateException if the snapshot does not exist.
177   * @exclude
178   */
179  public JPPFSnapshot addValue(final String label, final double value) {
180    return addValues(label, value, 1L);
181  }
182
183  /**
184   * Add the specified values to the snapshot with the specified label.
185   * @param label the label of the snapshot to add the values to.
186   * @param accumulatedValues the accumulated sum of the values to add.
187   * @param count the number of values in the accumalated values.
188   * @return a reference to the updated {@link JPPFSnapshot} object.
189   * @throws IllegalStateException if the snapshot does not exist.
190   * @exclude
191   */
192  public JPPFSnapshot addValues(final String label, final double accumulatedValues, final long count) {
193    JPPFSnapshot snapshot = snapshots.get(label);
194    if (snapshot == null) throw new IllegalStateException("snapshot '" + label + "' was either not created or removed!");
195    snapshot.addValues(accumulatedValues, count);
196    if (!listeners.isEmpty()) fireEvent(snapshot, EventType.UPDATED);
197    return snapshot;
198  }
199
200  /**
201   * Build a copy of this stats object.
202   * @return a new <code>JPPFStats</code> instance, populated with the current values
203   * of the fields in this stats object.
204   * @exclude
205   */
206  public JPPFStatistics copy() {
207    return new JPPFStatistics(snapshots);
208  }
209
210  @Override
211  public String toString() {
212    StringBuilder sb = new StringBuilder();
213    sb.append("JPPF statistics:");
214    for (Map.Entry<String, JPPFSnapshot> entry: snapshots.entrySet()) sb.append('\n').append(entry.getValue());
215    return sb.toString();
216  }
217
218  /**
219   * Reset all contained snapshots to their initial values.
220   * @exclude
221   */
222  public void reset() {
223    reset(null);
224  }
225
226  /**
227   * Reset all contained snapshots to their initial values.
228   * @param filter determines which snapshots will be reset.
229   * @exclude
230   */
231  public void reset(final JPPFStatistics.Filter filter) {
232    for (Map.Entry<String, JPPFSnapshot> entry: snapshots.entrySet()) {
233      JPPFSnapshot snapshot = entry.getValue();
234      if ((filter == null) || filter.accept(snapshot)) snapshot.reset();
235    }
236  }
237
238  /**
239   * Remove all snapshots from this object.
240   * @exclude
241   */
242  public void clear() {
243    snapshots.clear();
244  }
245
246  /**
247   * Get all the snapshots in this object.
248   * @return a collection of {@link JPPFSnapshot} instances.
249   */
250  public Collection<JPPFSnapshot> getSnapshots() {
251    return new ArrayList<>(snapshots.values());
252  }
253
254  /**
255   * Get the snapshots which satisfy the specified filter.
256   * @param filter determines which snapshots will be part of the returned collection.
257   * @return a collection of {@link JPPFSnapshot} instances, possibly empty but never null.
258   */
259  public Collection<JPPFSnapshot> getSnapshots(final Filter filter) {
260    List<JPPFSnapshot> list = new ArrayList<>(snapshots.size());
261    for (Map.Entry<String, JPPFSnapshot> entry: snapshots.entrySet()) {
262      JPPFSnapshot snapshot = entry.getValue();
263      if ((filter == null) || filter.accept(snapshot)) list.add(snapshot);
264    }
265    return list;
266  }
267
268  /**
269   * Add a listener to the list of listeners.
270   * This is equivalent to calling {@link #addListener(JPPFStatisticsListener, Filter) addListener(listener, null)}.
271   * @param listener the listener to add.
272   */
273  public void addListener(final JPPFStatisticsListener listener) {
274    addListener(listener, null);
275  }
276
277  /**
278   * Add a filtered listener to the list of listeners.
279   * @param listener the listener to add.
280   * @param filter the filter to apply to the listener. If {@code null}, then no filter is applied.
281   */
282  public void addListener(final JPPFStatisticsListener listener, final Filter filter) {
283    if (listener != null) listeners.add(new ListenerInfo(listener, filter));
284  }
285
286  /**
287   * Remove a listener to the list of listeners.
288   * @param listener the listener to remove.
289   */
290  public void removeListener(final JPPFStatisticsListener listener) {
291    if (listener != null) {
292      ListenerInfo toDelete = null;
293      for (ListenerInfo info: listeners) {
294        if (listener.equals(info.listener) && (info.filter == NOOP_FILTER)) {
295          toDelete = info;
296          break;
297        }
298      }
299      if (toDelete != null) listeners.remove(toDelete);
300    }
301  }
302
303  /**
304   * Remove a listener to the list of listeners.
305   * @param listener the listener to remove.
306   * @param filter the filter associated ith the listener to remove.
307   */
308  public void removeListener(final JPPFStatisticsListener listener, final Filter filter ) {
309    if (listener != null) listeners.remove(new ListenerInfo(listener, filter));
310  }
311
312  /**
313   * Notify all listeners that a snapshot was created.
314   * @param snapshot the snapshot for which an event occurs.
315   * @param type the type of event: created, removd, update.
316   */
317  private void fireEvent(final JPPFSnapshot snapshot, final EventType type) {
318    JPPFStatisticsEvent event = new JPPFStatisticsEvent(this, snapshot);
319    for (ListenerInfo info: listeners) {
320      if (info.filter.accept(snapshot)) {
321        switch(type) {
322          case ADDED:
323            info.listener.snapshotAdded(event);
324            break;
325          case REMOVED:
326            info.listener.snapshotRemoved(event);
327            break;
328          case UPDATED:
329            info.listener.snapshotUpdated(event);
330            break;
331        }
332      }
333    }
334  }
335
336  @Override
337  public Iterator<JPPFSnapshot> iterator() {
338    return snapshots.values().iterator();
339  }
340
341  /**
342   * A filter interface for snapshots.
343   */
344  public interface Filter {
345    /**
346     * Determines whether the specified snapshot is accepted by this filter.
347     * @param snapshot the snapshot to check.
348     * @return <code>true</code> if the snapshot is accepted, <code>false</code> otherwise.
349     */
350    boolean accept(JPPFSnapshot snapshot);
351  }
352
353  /**
354   * The possible types of events.
355   */
356  static enum EventType {
357    /**
358     * A snapshot was added.
359     */
360    ADDED,
361    /**
362     * A snapshot was removed.
363     */
364    REMOVED,
365    /**
366     * A snapshot was updated.
367     */
368    UPDATED
369  }
370
371  /**
372   * Association of a listener and filter.
373   */
374  private static class ListenerInfo {
375    /**
376     * The listener to filter.
377     */
378    public final JPPFStatisticsListener listener;
379    /**
380     * The filter to apply to the listener.
381     */
382    public final Filter filter;
383
384    /**
385     * Initialize this object.
386     * @param listener the listener to filter
387     * @param filter the filter to apply to the listener.
388     */
389    public ListenerInfo(final JPPFStatisticsListener listener, final Filter filter) {
390      if (listener == null) throw new IllegalArgumentException("the listener can never be null");
391      this.listener = listener;
392      this.filter = (filter == null) ? NOOP_FILTER : filter;
393    }
394
395    @Override
396    public int hashCode() {
397      int result = 31 + filter.hashCode();
398      result = 31 * result + listener.hashCode();
399      return result;
400    }
401
402    @Override
403    public boolean equals(final Object obj) {
404      if (this == obj) return true;
405      if (obj == null) return false;
406      if (getClass() != obj.getClass()) return false;
407      ListenerInfo other = (ListenerInfo) obj;
408      return listener.equals(other.listener) && filter.equals(other.filter);
409    }
410  }
411
412  /**
413   * Saves the state of this object to a stream.
414   * @param oos the stream to write to.
415   * @throws IOException if an I/O error occurs.
416   */
417  private void writeObject(final ObjectOutputStream oos) throws IOException {
418    oos.defaultWriteObject();
419  }
420
421  /**
422   * Restore the state of this object from a stream.
423   * @param ois the stream to read from.
424   * @throws IOException if an I/O error occurs.
425   * @throws ClassNotFoundException if a class cannot be found or initialized during deserialization.
426   */
427  private void readObject(final ObjectInputStream ois) throws IOException, ClassNotFoundException {
428    listeners = new CopyOnWriteArrayList<>();
429    ois.defaultReadObject();
430  }
431}