001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.performance;
018    
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.io.Serializable;
024    import org.apache.commons.math.stat.descriptive.SummaryStatistics;
025    
026    /**
027     * <p>Container for {@link SummaryStatistics} accumulated during 
028     * {@link ClientThread} executions.</p>
029     * 
030     * <p>Maintains a HashMap of {@link SummaryStatistics} instances with a composite
031     * key of the form (process,type).  "Process" typically identifies the client
032     * thread and "type" identifies the metric - e.g., "latency", "numActive."</p>
033     * 
034     * <p>{@link ClientThread#run()} adds one <code>SummaryStatistics</code>
035     * instance, with key = (current thread id,"latency").</p>
036     *
037     */
038    public class Statistics implements Serializable {
039        private HashMap<StatisticsKey,SummaryStatistics> data = 
040            new HashMap <StatisticsKey,SummaryStatistics>();
041        
042        /**
043         * Adds the results of the given SummaryStatistics instance under
044         * the key <process,type>
045         * 
046         * @param stats the SummaryStatistics whose results we are adding
047         * @param process name of the associated process
048         * @param type description of the associated metric
049         */
050        public synchronized void addStatistics(SummaryStatistics stats,
051                String process, String type) {
052            StatisticsKey key = new StatisticsKey(process, type);
053            data.put(key, stats);
054        }
055        
056        /**
057         * Retrieves the SummaryStatistics corresponding to the given
058         * process and type, if this exists; null otherwise.
059         * 
060         * @param process name of the associated process
061         * @param type description of the associated metric
062         * @return SummaryStatistics for the given <process,type>; null if there is
063         * no such element in the container
064         */
065        public synchronized SummaryStatistics getStatistics(String process,
066                String type) {
067            StatisticsKey key = new StatisticsKey(process, type);
068            return data.get(key);
069        }
070        
071        /**
072         * Returns the full list of SummaryStatistics corresponding to
073         * the given <code>type</code> - i.e, the list of statistics of the 
074         * given type across processes.  For example, 
075         * <code>getStatisticsByType("latency")</code> will return a list of latency
076         * summaries, one for each process, assuming "latency" is the name of 
077         * an accumulated metric.
078         * 
079         * @param type the type value to get statistics for
080         * @return the List of SummaryStatistics stored under the given type
081         */
082        public synchronized List<SummaryStatistics> getStatisticsByType(String type) {
083            ArrayList<SummaryStatistics> result = new ArrayList<SummaryStatistics>();
084            Iterator<StatisticsKey> it = data.keySet().iterator();
085            while (it.hasNext()) {
086                StatisticsKey key = it.next();
087                if (key.type.equals(type)) {
088                    result.add(data.get(key));
089                }
090            }
091            return result;
092        }
093        
094        /**
095         * Returns the full list of SummaryStatistics corresponding to
096         * the given <code>process</code> - i.e, the list of statistics of
097         * of different types maintained for the given process. 
098         * 
099         * @param process the process to get statistics for
100         * @return the List of SummaryStatistics for the given process
101         */
102        public synchronized List<SummaryStatistics> getStatisticsByProcess(
103                String process) {
104            ArrayList<SummaryStatistics> result = new ArrayList<SummaryStatistics>();
105            Iterator<StatisticsKey> it = data.keySet().iterator();
106            while (it.hasNext()) {
107                StatisticsKey key = it.next();
108                if (key.process.equals(process)) {
109                    result.add(data.get(key));
110                }
111            }
112            return result;
113        }
114        
115        /**
116         * <p>Returns a SummaryStatistics instance describing the mean of the given
117         * metric across processes - i.e., the "mean of the means", the 
118         * "min of the means" etc. More precisely, the returned SummaryStatistics
119         * describes the distribution of the individual process means for the given
120         * metric.</p>
121         * 
122         * <p>The same results could be obtained by iterating over the result of 
123         * {{@link #getStatisticsByType(String)} for the given <code>type</code>,
124         * extracting the mean and adding its value to a SummaryStatistics
125         * instance.</p>
126         * 
127         * @param type the metric to get summary mean statistics for
128         * @return a SummaryStatistics instance describing the process means for
129         * the given metric
130         */
131        public synchronized SummaryStatistics getMeanSummary(String type) {
132            SummaryStatistics result = new SummaryStatistics();
133            Iterator<StatisticsKey> it = data.keySet().iterator();
134            while (it.hasNext()) {
135                StatisticsKey key = it.next();
136                if (key.type.equals(type)) {
137                    result.addValue(data.get(key).getMean());
138                }
139            }
140            return result;
141        }
142        
143        /**
144         * Returns SummaryStatistics for the standard deviation of the given metric
145         * across processes.
146         * 
147         * @param type the metric to get summary standard deviation statistics for
148         * @return a SummaryStatistics instance describing the process standard
149         * deviations for the given metric
150         * @see #getMeanSummary(String)
151         */
152        public synchronized SummaryStatistics getStdSummary(String type) {
153            SummaryStatistics result = new SummaryStatistics();
154            Iterator<StatisticsKey> it = data.keySet().iterator();
155            while (it.hasNext()) {
156                StatisticsKey key = it.next();
157                if (key.type.equals(type)) {
158                    result.addValue(data.get(key).getStandardDeviation());
159                }
160            }
161            return result;
162        }
163        
164        /**
165         * Returns SummaryStatistics for the minimum of the given metric across
166         * processes.
167         * 
168         * @param type the metric to get summary minimum statistics for
169         * @return a SummaryStatistics instance describing the process minima
170         * for the given metric
171         * @see #getMeanSummary(String)
172         */
173        public synchronized SummaryStatistics getMinSummary(String type) {
174            SummaryStatistics result = new SummaryStatistics();
175            Iterator<StatisticsKey> it = data.keySet().iterator();
176            while (it.hasNext()) {
177                StatisticsKey key = it.next();
178                if (key.type.equals(type)) {
179                    result.addValue(data.get(key).getMin());
180                }
181            }
182            return result;
183        }
184        
185        /**
186         * Returns SummaryStatistics for the maximum of the given metric across
187         * processes.
188         * 
189         * @param type the metric to get summary maximum statistics for
190         * @return a SummaryStatistics describing the process maxima
191         * for the given metric
192         * @see #getMeanSummary(String)
193         */
194        public synchronized SummaryStatistics getMaxSummary(String type) {
195            SummaryStatistics result = new SummaryStatistics();
196            Iterator<StatisticsKey> it = data.keySet().iterator();
197            while (it.hasNext()) {
198                StatisticsKey key = it.next();
199                if (key.type.equals(type)) {
200                    result.addValue(data.get(key).getMax());
201                }
202            }
203            return result;
204        }
205        
206        /**
207         * Returns the List of processes corresponding to statistics.
208         * 
209         * @return List of processes represented in the container
210         */
211        public synchronized List<String> getProcesses() {
212            ArrayList<String> result = new ArrayList<String>();
213            Iterator<StatisticsKey> it = data.keySet().iterator();
214            while (it.hasNext()) {
215                String currProcess = it.next().process;
216                if (!result.contains(currProcess)) {
217                    result.add(currProcess);
218                }
219            }
220            return result;
221        }
222        
223        /**
224         * Returns the List of types corresponding to statistics.
225         * 
226         * @return List of types represented in the container
227         */
228        public synchronized List<String> getTypes() {
229            ArrayList<String> result = new ArrayList<String>();
230            Iterator<StatisticsKey> it = data.keySet().iterator();
231            while (it.hasNext()) {
232                String currType = it.next().type;
233                if (!result.contains(currType)) {
234                    result.add(currType);
235                }
236            }
237            return result;
238        }
239        
240        /**
241         * Computes and formats display of summary statistics by type, across
242         * processes. 
243         * 
244         * @return String representing summaries for each metric 
245         */
246        public synchronized String displayOverallSummary() {
247            Iterator<String> metricsIterator = getTypes().iterator();
248            StringBuffer buffer = new StringBuffer();
249            while (metricsIterator.hasNext()) {
250                String metric = metricsIterator.next();
251                buffer.append("Overall statistics for the mean ");
252                buffer.append(metric.toUpperCase());
253                buffer.append("\n");
254                buffer.append(getMeanSummary(metric).toString());
255                buffer.append("********************************************\n");
256                buffer.append("Overall statistics for the standard deviation ");
257                buffer.append(metric.toUpperCase());
258                buffer.append("\n");
259                buffer.append(getStdSummary(metric).toString());
260                buffer.append("********************************************\n");
261                buffer.append("Overall statistics for the min ");
262                buffer.append(metric.toUpperCase());
263                buffer.append("\n");
264                buffer.append(getMinSummary(metric).toString());
265                buffer.append("********************************************\n");
266                buffer.append("Overall statistics for the max ");
267                buffer.append(metric.toUpperCase());
268                buffer.append("\n");
269                buffer.append(getMaxSummary(metric).toString());
270                buffer.append("********************************************\n");
271            } 
272            return buffer.toString();
273        }
274        
275        /**
276         * Displays statistics for the given process
277         * 
278         * @param process the process to retrieve metrics for
279         * @return String representing all currently defined statistics for the
280         * given process
281         */
282        public synchronized String displayProcessStatistics(String process) {
283            Iterator<String> metricsIterator = getTypes().iterator();
284            StringBuffer buffer = new StringBuffer();
285            while (metricsIterator.hasNext()) {
286                String metric = metricsIterator.next();
287                buffer.append("*********************************************\n");
288                buffer.append(metric.toUpperCase());
289                buffer.append(" for ");
290                buffer.append(process);
291                buffer.append(" ");
292                buffer.append(getStatistics(process, metric).toString());
293                buffer.append("\n********************************************\n");
294            }
295            return buffer.toString();
296        }
297        
298        
299        /**
300         * Composite key (<process,type>).
301         */
302        private static class StatisticsKey implements Serializable {
303            public StatisticsKey(String process, String type) {
304                this.process = process;
305                this.type = type;
306            }
307            private String process = null;
308            private String type = null;
309            public boolean equals(Object obj) {
310                if (!(obj instanceof StatisticsKey) || obj == null) {
311                    return false;
312                } else {
313                    StatisticsKey other = (StatisticsKey) obj;
314                    return (other.process.equals(this.process)) &&
315                        (other.type.equals(this.type));  
316                }
317            }
318            public int hashCode() {
319                return 7 + 11 * process.hashCode() + 17 * type.hashCode();
320            } 
321            public String getType() {
322                return type;
323            }
324            public String getProcess() {
325                return process;
326            }
327        }
328    
329    }