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