001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2011-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.rolap;
011
012import mondrian.calc.*;
013import mondrian.calc.impl.*;
014import mondrian.olap.*;
015import mondrian.olap.type.SetType;
016import mondrian.olap.type.Type;
017
018import java.util.*;
019
020/**
021 * Evaluator that collects profiling information as it evaluates expressions.
022 *
023 * <p>TODO: Cleanup tasks as part of explain/profiling project:
024 *
025 * <p>1. Obsolete AbstractCalc.calcs member, AbstractCalc.getCalcs(), and
026 *     Calc[] constructor parameter to many Calc subclasses. Store the
027 *     tree structure (children of a calc, parent of a calc) in
028 *     RolapEvaluatorRoot.compiledExps.
029 *
030 * <p>Rationale: Children calcs are
031 *     used in about 50 places, but mostly for dependency-checking (e.g.
032 *     {@link mondrian.calc.impl.AbstractCalc#anyDepends}). A few places uses
033 *     the calcs array but should use more strongly typed members. e.g.
034 *     FilterFunDef.MutableMemberIterCalc should have data members
035 *     'MemberListCalc listCalc' and 'BooleanCalc conditionCalc'.
036 *
037 * <p>2. Split Query into parse tree, plan, statement. Fits better into the
038 *     createStatement - prepare - execute JDBC lifecycle. Currently Query has
039 *     aspects of all of these, and some other state is held in RolapResult
040 *     (computed in the constructor, unfortunately) and RolapEvaluatorRoot.
041 *     This cleanup may not be essential for the explain/profiling task but
042 *     should happen soon afterwards.
043 *
044 * @author jhyde
045 * @since October, 2010
046  */
047public class RolapProfilingEvaluator extends RolapEvaluator {
048
049    /**
050     * Creates a profiling evaluator.
051     *
052     * @param root Shared context between this evaluator and its children
053     */
054    RolapProfilingEvaluator(RolapEvaluatorRoot root) {
055        super(root);
056    }
057
058    /**
059     * Creates a child evaluator.
060     *
061     * @param root Root evaluation context
062     * @param evaluator Parent evaluator
063     */
064    private RolapProfilingEvaluator(
065        RolapEvaluatorRoot root,
066        RolapProfilingEvaluator evaluator,
067        List<List<Member>> aggregationList)
068    {
069        super(
070            root,
071            evaluator,
072            aggregationList);
073    }
074
075    @Override
076    protected RolapEvaluator _push(List<List<Member>> aggregationList) {
077        return new RolapProfilingEvaluator(root, this, aggregationList);
078    }
079
080    /**
081     * Expression compiler which introduces dependency testing.
082     *
083     * <p>It also checks that the caller does not modify lists unless it has
084     * explicitly asked for a mutable list.
085     */
086    static class ProfilingEvaluatorCompiler extends DelegatingExpCompiler {
087        ProfilingEvaluatorCompiler(ExpCompiler compiler) {
088            super(compiler);
089        }
090
091        protected Calc afterCompile(Exp exp, Calc calc, boolean mutable) {
092            calc = super.afterCompile(exp, calc, mutable);
093            if (calc == null) {
094                return null;
095            }
096            if (calc.getType() instanceof SetType) {
097                return new ProfilingIterCalc(
098                    exp,
099                    calc);
100            } else {
101                return new ProfilingScalarCalc(
102                    exp,
103                    calc);
104            }
105        }
106    }
107
108    /**
109     * Compiled expression that wraps a list or iterator expression and gathers
110     * profiling information.
111     */
112    private static class ProfilingIterCalc extends GenericIterCalc {
113        private final Calc calc;
114        private int callCount;
115        private long callMillis;
116        private long elementCount;
117        private long elementSquaredCount;
118
119        protected ProfilingIterCalc(Exp exp, Calc calc) {
120            super(exp, new Calc[] {calc});
121            this.calc = calc;
122        }
123
124        @Override
125        public boolean isWrapperFor(Class<?> iface) {
126            return calc.isWrapperFor(iface);
127        }
128
129        @Override
130        public <T> T unwrap(Class<T> iface) {
131            return calc.unwrap(iface);
132        }
133
134        @Override
135        public SetType getType() {
136            return (SetType) calc.getType();
137        }
138
139        @Override
140        public ResultStyle getResultStyle() {
141            return calc.getResultStyle();
142        }
143
144        @Override
145        public boolean dependsOn(Hierarchy hierarchy) {
146            return calc.dependsOn(hierarchy);
147        }
148
149        public Object evaluate(Evaluator evaluator) {
150            ++callCount;
151            long start = System.currentTimeMillis();
152            final Object o = calc.evaluate(evaluator);
153            long end = System.currentTimeMillis();
154            callMillis += (end - start);
155            if (o instanceof Collection) {
156                long size = ((Collection) o).size();
157                elementCount += size;
158                elementSquaredCount += size * size;
159            }
160            return o;
161        }
162
163        @Override
164        public void accept(CalcWriter calcWriter) {
165            // Populate arguments with statistics.
166            final Map<String, Object> argumentMap =
167                new LinkedHashMap<String, Object>();
168            if (calcWriter.enableProfiling()) {
169                argumentMap.put("callCount", callCount);
170                argumentMap.put("callMillis", callMillis);
171                argumentMap.put("elementCount", elementCount);
172                argumentMap.put("elementSquaredCount", elementSquaredCount);
173            }
174            calcWriter.setParentArgs(calc, argumentMap);
175
176            // Invoke writer on our child calc. This node won't appear in the
177            // query plan, but the arguments we just created will appear as
178            // arguments of the child calc.
179            calc.accept(calcWriter);
180        }
181    }
182
183    /**
184     * Compiled expression that wraps a scalar expression and gathers profiling
185     * information.
186     */
187    private static class ProfilingScalarCalc extends GenericCalc {
188        private final Calc calc;
189        private int callCount;
190        private long callMillis;
191
192        ProfilingScalarCalc(Exp exp, Calc calc) {
193            super(exp, new Calc[] {calc});
194            this.calc = calc;
195        }
196
197        @Override
198        public boolean isWrapperFor(Class<?> iface) {
199            return calc.isWrapperFor(iface);
200        }
201
202        @Override
203        public <T> T unwrap(Class<T> iface) {
204            return calc.unwrap(iface);
205        }
206
207        @Override
208        public Type getType() {
209            return calc.getType();
210        }
211
212        @Override
213        public Calc[] getCalcs() {
214            return ((AbstractCalc) calc).getCalcs();
215        }
216
217        @Override
218        public boolean dependsOn(Hierarchy hierarchy) {
219            return calc.dependsOn(hierarchy);
220        }
221
222        public Object evaluate(Evaluator evaluator) {
223            ++callCount;
224            long start = System.currentTimeMillis();
225            final Object o = calc.evaluate(evaluator);
226            long end = System.currentTimeMillis();
227            callMillis += (end - start);
228            return o;
229        }
230
231        @Override
232        public void accept(CalcWriter calcWriter) {
233            final Map<String, Object> argumentMap =
234                new LinkedHashMap<String, Object>();
235            if (calcWriter.enableProfiling()) {
236                argumentMap.put("callCount", callCount);
237                argumentMap.put("callMillis", callMillis);
238            }
239            calcWriter.setParentArgs(calc, argumentMap);
240
241            // Invoke writer on our child calc. This node won't appear in the
242            // query plan, but the arguments we just created will appear as
243            // arguments of the child calc.
244            calc.accept(calcWriter);
245        }
246    }
247}
248
249// End RolapProfilingEvaluator.java