1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.test.tilebenchmark;
18
19import android.content.res.Resources;
20import android.graphics.Canvas;
21import android.graphics.Color;
22import android.graphics.Paint;
23import android.graphics.Rect;
24import android.graphics.drawable.ShapeDrawable;
25
26import com.test.tilebenchmark.RunData.TileData;
27
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.HashMap;
31
32public class PlaybackGraphs {
33    private static final int BAR_WIDTH = PlaybackView.TILE_SCALE * 3;
34    private static final float CANVAS_SCALE = 0.2f;
35    private static final double IDEAL_FRAMES = 60;
36    private static final int LABELOFFSET = 100;
37    private static Paint whiteLabels;
38
39    private static double viewportCoverage(TileData view, TileData tile) {
40        if (tile.left < (view.right * view.scale)
41                && tile.right >= (view.left * view.scale)
42                && tile.top < (view.bottom * view.scale)
43                && tile.bottom >= (view.top * view.scale)) {
44            return 1.0f;
45        }
46        return 0.0f;
47    }
48
49    protected interface MetricGen {
50        public double getValue(TileData[] frame);
51
52        public double getMax();
53
54        public int getLabelId();
55    };
56
57    protected static MetricGen[] Metrics = new MetricGen[] {
58            new MetricGen() {
59                // framerate graph
60                @Override
61                public double getValue(TileData[] frame) {
62                    int renderTimeUS = frame[0].level;
63                    return 1.0e6f / renderTimeUS;
64                }
65
66                @Override
67                public double getMax() {
68                    return IDEAL_FRAMES;
69                }
70
71                @Override
72                public int getLabelId() {
73                    return R.string.frames_per_second;
74                }
75            }, new MetricGen() {
76                // coverage graph
77                @Override
78                public double getValue(TileData[] frame) {
79                    double total = 0, totalCount = 0;
80                    for (int tileID = 1; tileID < frame.length; tileID++) {
81                        TileData data = frame[tileID];
82                        double coverage = viewportCoverage(frame[0], data);
83                        total += coverage * (data.isReady ? 100 : 0);
84                        totalCount += coverage;
85                    }
86                    if (totalCount == 0) {
87                        return -1;
88                    }
89                    return total / totalCount;
90                }
91
92                @Override
93                public double getMax() {
94                    return 100;
95                }
96
97                @Override
98                public int getLabelId() {
99                    return R.string.viewport_coverage;
100                }
101            }
102    };
103
104    protected interface StatGen {
105        public double getValue(double sortedValues[]);
106
107        public int getLabelId();
108    }
109
110    public static double getPercentile(double sortedValues[], double ratioAbove) {
111        if (sortedValues.length == 0)
112            return -1;
113
114        double index = ratioAbove * (sortedValues.length - 1);
115        int intIndex = (int) Math.floor(index);
116        if (index == intIndex) {
117            return sortedValues[intIndex];
118        }
119        double alpha = index - intIndex;
120        return sortedValues[intIndex] * (1 - alpha)
121                + sortedValues[intIndex + 1] * (alpha);
122    }
123
124    public static double getMean(double sortedValues[]) {
125        if (sortedValues.length == 0)
126            return -1;
127
128        double agg = 0;
129        for (double val : sortedValues) {
130            agg += val;
131        }
132        return agg / sortedValues.length;
133    }
134
135    public static double getStdDev(double sortedValues[]) {
136        if (sortedValues.length == 0)
137            return -1;
138
139        double agg = 0;
140        double sqrAgg = 0;
141        for (double val : sortedValues) {
142            agg += val;
143            sqrAgg += val*val;
144        }
145        double mean = agg / sortedValues.length;
146        return Math.sqrt((sqrAgg / sortedValues.length) - (mean * mean));
147    }
148
149    protected static StatGen[] Stats = new StatGen[] {
150            new StatGen() {
151                @Override
152                public double getValue(double[] sortedValues) {
153                    return getPercentile(sortedValues, 0.25);
154                }
155
156                @Override
157                public int getLabelId() {
158                    return R.string.percentile_25;
159                }
160            }, new StatGen() {
161                @Override
162                public double getValue(double[] sortedValues) {
163                    return getPercentile(sortedValues, 0.5);
164                }
165
166                @Override
167                public int getLabelId() {
168                    return R.string.percentile_50;
169                }
170            }, new StatGen() {
171                @Override
172                public double getValue(double[] sortedValues) {
173                    return getPercentile(sortedValues, 0.75);
174                }
175
176                @Override
177                public int getLabelId() {
178                    return R.string.percentile_75;
179                }
180            }, new StatGen() {
181                @Override
182                public double getValue(double[] sortedValues) {
183                    return getStdDev(sortedValues);
184                }
185
186                @Override
187                public int getLabelId() {
188                    return R.string.std_dev;
189                }
190            }, new StatGen() {
191                @Override
192                public double getValue(double[] sortedValues) {
193                    return getMean(sortedValues);
194                }
195
196                @Override
197                public int getLabelId() {
198                    return R.string.mean;
199                }
200            },
201    };
202
203    public PlaybackGraphs() {
204        whiteLabels = new Paint();
205        whiteLabels.setColor(Color.WHITE);
206        whiteLabels.setTextSize(PlaybackView.TILE_SCALE / 3);
207    }
208
209    private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>();
210    protected final double[][] mStats = new double[Metrics.length][Stats.length];
211    protected HashMap<String, Double> mSingleStats;
212
213    private void gatherFrameMetric(int metricIndex, double metricValues[], RunData data) {
214        // create graph out of rectangles, one per frame
215        int lastBar = 0;
216        for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
217            TileData frame[] = data.frames[frameIndex];
218            int newBar = (int)((frame[0].top + frame[0].bottom) * frame[0].scale / 2.0f);
219
220            MetricGen s = Metrics[metricIndex];
221            double absoluteValue = s.getValue(frame);
222            double relativeValue = absoluteValue / s.getMax();
223            relativeValue = Math.min(1,relativeValue);
224            relativeValue = Math.max(0,relativeValue);
225            int rightPos = (int) (-BAR_WIDTH * metricIndex);
226            int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
227
228            ShapeDrawable graphBar = new ShapeDrawable();
229            graphBar.getPaint().setColor(Color.BLUE);
230            graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
231
232            mShapes.add(graphBar);
233            metricValues[frameIndex] = absoluteValue;
234            lastBar = newBar;
235        }
236    }
237
238    public void setData(RunData data) {
239        mShapes.clear();
240        double metricValues[] = new double[data.frames.length];
241
242        mSingleStats = data.singleStats;
243
244        if (data.frames.length == 0) {
245            return;
246        }
247
248        for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
249            // calculate metric based on list of frames
250            gatherFrameMetric(metricIndex, metricValues, data);
251
252            // store aggregate statistics per metric (median, and similar)
253            Arrays.sort(metricValues);
254            for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
255                mStats[metricIndex][statIndex] =
256                        Stats[statIndex].getValue(metricValues);
257            }
258        }
259    }
260
261    public void drawVerticalShiftedShapes(Canvas canvas,
262            ArrayList<ShapeDrawable> shapes) {
263        // Shapes drawn here are drawn relative to the viewRect
264        Rect viewRect = shapes.get(shapes.size() - 1).getBounds();
265        canvas.translate(0, 5 * PlaybackView.TILE_SCALE - viewRect.top);
266
267        for (ShapeDrawable shape : mShapes) {
268            shape.draw(canvas);
269        }
270        for (ShapeDrawable shape : shapes) {
271            shape.draw(canvas);
272        }
273    }
274
275    public void draw(Canvas canvas, ArrayList<ShapeDrawable> shapes,
276            ArrayList<String> strings, Resources resources) {
277        canvas.scale(CANVAS_SCALE, CANVAS_SCALE);
278
279        canvas.translate(BAR_WIDTH * Metrics.length, 0);
280
281        canvas.save();
282        drawVerticalShiftedShapes(canvas, shapes);
283        canvas.restore();
284
285        for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
286            String label = resources.getString(
287                    Metrics[metricIndex].getLabelId());
288            int xPos = (metricIndex + 1) * -BAR_WIDTH;
289            int yPos = LABELOFFSET;
290            canvas.drawText(label, xPos, yPos, whiteLabels);
291            for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
292                String statLabel = resources.getString(
293                        Stats[statIndex].getLabelId()).substring(0,3);
294                label = statLabel + " " + resources.getString(
295                        R.string.format_stat, mStats[metricIndex][statIndex]);
296                yPos = LABELOFFSET + (1 + statIndex) * PlaybackView.TILE_SCALE
297                        / 2;
298                canvas.drawText(label, xPos, yPos, whiteLabels);
299            }
300        }
301        for (int stringIndex = 0; stringIndex < strings.size(); stringIndex++) {
302            int yPos = LABELOFFSET + stringIndex * PlaybackView.TILE_SCALE / 2;
303            canvas.drawText(strings.get(stringIndex), 0, yPos, whiteLabels);
304        }
305    }
306}
307