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