001: /*
002: * Copyright 2006-2007 The Scriptella Project Team.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package scriptella.execution;
017:
018: import scriptella.configuration.Location;
019: import scriptella.spi.Connection;
020:
021: import java.util.ArrayList;
022: import java.util.Date;
023:
024: /**
025: * A builder for execution statistics.
026: * <p>This class collects runtime ETL execution statistics, the usage contract is the following:</p>
027: * <ul>
028: * <li>{@link #etlStarted()} invoked on ETL start.
029: * <li>Call {@link #elementStarted(Location,Connection)} before executing an element.
030: * <li>Call {@link #elementExecuted()} or {@link #elementFailed()} after executing an element.
031: * <li>{@link #etlComplete()} invoked when ETL completes.
032: * <li>{@link #getStatistics() Obtain statistics} after ETL completes.
033: * </ul>
034: * <em>Notes:</em>
035: * <ul>
036: * <li>Start/End element callbacks sequence must comply with executing elements position in a XML file.
037: * <li>This class is not thread safe.
038: * </ul>
039: *
040: * @author Fyodor Kupolov
041: * @version 1.0
042: */
043: public class ExecutionStatisticsBuilder {
044: private ExecutionStatistics executionStatistics;
045:
046: //Execution stack for nested elements
047: protected ElementsStack executionStack = new ElementsStack();
048:
049: /**
050: * Called when new element execution started.
051: *
052: * @param loc element location.
053: */
054: public void elementStarted(final Location loc, Connection connection) {
055: ExecutionStatistics.ElementInfo ei = getInfo(loc);
056: executionStack.push(ei);
057: ei.statementsOnStart = connection.getExecutedStatementsCount();
058: ei.connection = connection;
059: ei.started = System.nanoTime();
060: }
061:
062: /**
063: * This method is called when element has been executed.
064: */
065: public void elementExecuted() {
066: setElementState(true);
067: }
068:
069: /**
070: * Invoked on ETL start
071: */
072: public void etlStarted() {
073: executionStatistics = new ExecutionStatistics();
074: executionStatistics.setStarted(new Date());
075: }
076:
077: /**
078: * Invoked on ETL completion.
079: */
080: public void etlComplete() {
081: if (executionStatistics == null) {
082: throw new IllegalStateException("etlStarted not called");
083: }
084:
085: //assume that statistics is obtained immediately after the execution
086: executionStatistics.setFinished(new Date());
087: }
088:
089: /**
090: * Calculates execution time and statements number for the completed element.
091: */
092: private void setElementState(boolean ok) {
093: ExecutionStatistics.ElementInfo ended = executionStack.pop();
094: long ti = System.nanoTime() - ended.started;
095: ended.workingTime += ti; //increase the total working time for element
096: if (ended.workingTime < 0) {
097: ended.workingTime = 0;
098: }
099: final Connection con = ended.connection;
100: ended.connection = null; //clear the connection to avoid leaks
101: long conStatements = con.getExecutedStatementsCount();
102: long elStatements = conStatements - ended.statementsOnStart;
103: if (ok) {
104: ended.okCount++;
105: } else {
106: ended.failedCount++;
107: }
108:
109: if (elStatements > 0) {
110: ended.statements += elStatements;
111: executionStatistics.statements += elStatements;
112: }
113:
114: //Exclude this element time from parent elements
115: //Also find the parent elements with the same connection and decrement their number of statements
116: for (int i = executionStack.size() - 1; i >= 0; i--) {
117: final ExecutionStatistics.ElementInfo parent = executionStack
118: .get(i);
119: parent.workingTime -= ti;
120: if (parent.connection == con) { //if the same objects
121: parent.statementsOnStart += elStatements;
122: }
123: }
124:
125: }
126:
127: public void elementFailed() {
128: setElementState(false);
129: }
130:
131: private ExecutionStatistics.ElementInfo getInfo(final Location loc) {
132: if (executionStatistics == null) {
133: throw new IllegalStateException(
134: "etlStarted must be invoked prior to calling this method");
135: }
136: ExecutionStatistics.ElementInfo ei = executionStatistics.elements
137: .get(loc.getXPath());
138:
139: if (ei == null) {
140: ei = new ExecutionStatistics.ElementInfo();
141: ei.id = loc.getXPath();
142: executionStatistics.elements.put(loc.getXPath(), ei);
143: }
144:
145: return ei;
146: }
147:
148: public ExecutionStatistics getStatistics() {
149: return executionStatistics;
150: }
151:
152: /**
153: * A non-synchronized faster replacement for {@link java.util.Stack}.
154: */
155: static final class ElementsStack extends
156: ArrayList<ExecutionStatistics.ElementInfo> {
157: public ExecutionStatistics.ElementInfo pop() {
158: return remove(size() - 1);
159: }
160:
161: public void push(ExecutionStatistics.ElementInfo element) {
162: add(element);
163: }
164: }
165: }
|