View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.beans.stats.providers;
12  
13  import jakarta.servlet.http.HttpServletRequest;
14  
15  import java.util.ArrayList;
16  import java.util.Collections;
17  import java.util.Comparator;
18  import java.util.List;
19  import java.util.ListIterator;
20  import java.util.Map;
21  
22  import org.jfree.data.xy.DefaultTableXYDataset;
23  import org.jfree.data.xy.XYDataItem;
24  
25  import psiprobe.model.stats.StatsCollection;
26  
27  /**
28   * Retrieves stats series with names that start with the statNamePrefix. Either all matching series
29   * or only "top" N ones can be retrieved. Determines top series by comparing max moving avg values.
30   * Derrives legend entries from series names by removing the statNamePrefix. Ignores series param
31   * (sp) and legend (s...l) request parameters.
32   */
33  public class MultipleSeriesProvider extends AbstractSeriesProvider {
34  
35    /** The stat name prefix. */
36    private String statNamePrefix;
37  
38    /** The top. */
39    private int top;
40  
41    /** The moving avg frame. */
42    private int movingAvgFrame;
43  
44    /**
45     * Gets the stat name prefix.
46     *
47     * @return the stat name prefix
48     */
49    public String getStatNamePrefix() {
50      return statNamePrefix;
51    }
52  
53    /**
54     * Sets the stat name prefix.
55     *
56     * @param statNamePrefix - only series with names that start with statNamePrefix are retrieved.
57     */
58    public void setStatNamePrefix(String statNamePrefix) {
59      this.statNamePrefix = statNamePrefix;
60    }
61  
62    /**
63     * Gets the top.
64     *
65     * @return the top
66     */
67    public int getTop() {
68      return top;
69    }
70  
71    /**
72     * Sets the top.
73     *
74     * @param top - the number of top series to retrieve. If this value is greater than 0, only this
75     *        many series with the greatest max moving avg values are retrieved.
76     */
77    public void setTop(int top) {
78      this.top = top;
79    }
80  
81    /**
82     * Gets the moving avg frame.
83     *
84     * @return the moving avg frame
85     */
86    public int getMovingAvgFrame() {
87      return movingAvgFrame;
88    }
89  
90    /**
91     * Sets the moving avg frame.
92     *
93     * @param movingAvgFrame - if this value is greater than 0, a moving avg value is calculated for
94     *        every series using every Nth value, where N % movingAvgFrame == 0. Top series are
95     *        identified based on a max moving avg value of each series. If the movingAvgFrame equals
96     *        to 0, top series are determined based on a simple avg of all series values.
97     */
98    public void setMovingAvgFrame(int movingAvgFrame) {
99      this.movingAvgFrame = movingAvgFrame;
100   }
101 
102   @Override
103   public void populate(DefaultTableXYDataset dataset, StatsCollection statsCollection,
104       HttpServletRequest request) {
105 
106     Map<String, List<XYDataItem>> statMap = statsCollection.getStatsByPrefix(statNamePrefix);
107     boolean useTop = getTop() > 0 && getTop() < statMap.size();
108     List<Series> seriesList = new ArrayList<>(statMap.size());
109 
110     for (Map.Entry<String, List<XYDataItem>> entry : statMap.entrySet()) {
111       Series ser = new Series(entry);
112       if (useTop) {
113         ser.calculateAvg();
114       }
115       seriesList.add(ser);
116     }
117 
118     if (useTop) {
119       // sorting stats by the avg value to identify the top series
120       Collections.sort(seriesList,
121           (s1, s2) -> Double.compare(s1.avg, s2.avg) == 0 ? s1.key.compareTo(s2.key)
122               : Double.compare(s1.avg, s2.avg) > 0 ? -1 : 1);
123 
124       // keeping only the top series in the list
125       for (ListIterator<Series> i = seriesList.listIterator(getTop()); i.hasNext();) {
126         i.next();
127         i.remove();
128       }
129     }
130 
131     // sorting the remaining series by name
132     Collections.sort(seriesList, Comparator.comparing(s1 -> s1.key));
133 
134     for (Series ser : seriesList) {
135       synchronized (ser.stats) {
136         dataset.addSeries(toSeries(ser.key, ser.stats));
137       }
138     }
139   }
140 
141   /**
142    * The Class Series.
143    */
144   // a helper class that holds series and calculates an avg value
145   private class Series {
146 
147     /** The key. */
148     final String key;
149 
150     /** The stats. */
151     final List<XYDataItem> stats;
152 
153     /** The avg. */
154     double avg = 0;
155 
156     /**
157      * Instantiates a new series.
158      *
159      * @param en the en
160      */
161     Series(Map.Entry<String, List<XYDataItem>> en) {
162       key = en.getKey().substring(statNamePrefix.length());
163       stats = en.getValue();
164     }
165 
166     /**
167      * Calculate avg.
168      */
169     // calculating an avg value that is used for identifying the top series
170     void calculateAvg() {
171       long sum = 0;
172       int count = 1;
173 
174       synchronized (stats) {
175         boolean useMovingAvg = getMovingAvgFrame() > 0 && getMovingAvgFrame() < stats.size();
176 
177         for (ListIterator<XYDataItem> it = stats.listIterator(); it.hasNext();) {
178           XYDataItem xy = it.next();
179           sum += xy.getY().longValue();
180 
181           if ((useMovingAvg && count % getMovingAvgFrame() == 0) || !it.hasNext()) {
182             double thisAvg = (double) sum / count;
183             if (thisAvg > avg) {
184               avg = thisAvg;
185             }
186             sum = 0;
187             count = 1;
188           } else {
189             count++;
190           }
191         }
192       }
193     }
194   }
195 }