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.spi.Connection;
019:
020: import java.text.DecimalFormat;
021: import java.text.Format;
022: import java.text.NumberFormat;
023: import java.util.Collection;
024: import java.util.Date;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.LinkedHashMap;
028: import java.util.Map;
029:
030: /**
031: * Represents script execution statistics
032: * <p>The statistics is groupped by script elements i.e. script or query.
033: *
034: * @author Fyodor Kupolov
035: * @version 1.0
036: */
037: public class ExecutionStatistics {
038: int statements;
039: Map<String, ElementInfo> elements = new LinkedHashMap<String, ElementInfo>();
040: private Date started;
041: private Date finished;
042: private static final int MINUTE_MILLIS = 60 * 1000;
043: private static final int HOUR_MILLIS = 60 * MINUTE_MILLIS;
044: private static final int DAY_MILLIS = 24 * HOUR_MILLIS;
045: static final String DOUBLE_FORMAT_PTR = "0.##";
046:
047: /**
048: * @return number of statements executed for all elements.
049: */
050: public int getExecutedStatementsCount() {
051: return statements;
052: }
053:
054: public Collection<ElementInfo> getElements() {
055: return elements.values();
056: }
057:
058: /**
059: * Returns the statistics on executed categories, e.g.
060: * queries-5times, scripts-10times.
061: * <p>Note the category names are xml element names, no plural form used.
062: *
063: * @return xmlelement->count map .
064: */
065: public Map<String, Integer> getCategoriesStatistics() {
066: Map<String, Integer> res = new HashMap<String, Integer>();
067: for (String xpath : elements.keySet()) {
068: String elementName = getElementName(xpath);
069: Integer cnt = res.get(elementName);
070: if (cnt == null) {
071: cnt = 0;
072: }
073: res.put(elementName, ++cnt); //autoboxing works fine on small numbers
074: }
075: return res;
076: }
077:
078: public String toString() {
079: final Collection<ElementInfo> elements = getElements();
080: if (elements.isEmpty()) {
081: return "No elements executed";
082: }
083: StringBuilder sb = new StringBuilder(1024);
084: NumberFormat doubleFormat = new DecimalFormat(DOUBLE_FORMAT_PTR);
085: sb.append("Executed ");
086:
087: Map<String, Integer> cats = getCategoriesStatistics();
088: for (Iterator<Map.Entry<String, Integer>> it = cats.entrySet()
089: .iterator(); it.hasNext();) {
090: Map.Entry<String, Integer> category = it.next();
091: appendPlural(sb, category.getValue(), category.getKey());
092: if (it.hasNext()) {
093: sb.append(", ");
094: }
095: }
096: if (statements > 0) {
097: sb.append(", ");
098: appendPlural(sb, statements, "statement");
099: }
100:
101: sb.append('\n');
102:
103: for (ElementInfo ei : elements) {
104: sb.append(ei.id).append(":");
105:
106: if (ei.okCount > 0) {
107: sb.append(" Element successfully executed");
108: if (ei.okCount > 1) {
109: sb.append(' ');
110: appendPlural(sb, ei.okCount, "time");
111: }
112: if (ei.statements > 0) {
113: sb.append(" (");
114: appendPlural(sb, ei.statements, "statement")
115: .append(')');
116: }
117: sb.append('.');
118: }
119:
120: if (ei.failedCount > 0) {
121: sb.append(" Element failed ");
122: appendPlural(sb, ei.failedCount, "time");
123: sb.append('.');
124: }
125: sb.append(" Working time ")
126: .append(ei.workingTime / 1000000).append(
127: " milliseconds.");
128: //Output throughput
129: double throughput = ei.getThroughput();
130: if (throughput >= 0) {
131: sb.append(" Avg throughput: ").append(
132: doubleFormat.format(throughput)).append(
133: " statements/sec.");
134: }
135: sb.append('\n');
136:
137: }
138: long totalTime = getTotalTime();
139: if (totalTime >= 0) {
140: sb.append("Total working time:");
141: appendTotalTimeDuration(totalTime, sb, doubleFormat);
142: }
143: return sb.toString();
144: }
145:
146: /**
147: * A helper method to get element name from simplified location xpath.
148: *
149: * @param xpath xpath to get referenced element name.
150: * @return element name.
151: */
152: private static String getElementName(String xpath) {
153: int slash = xpath.lastIndexOf('/');
154: int br = xpath.lastIndexOf('[');
155: return xpath.substring(slash + 1, br);
156: }
157:
158: private static StringBuilder appendPlural(final StringBuilder sb,
159: final long num, final String singularNoun) {
160: sb.append(num).append(' ');
161: if (num > 1) { //plural form
162: if ("query".equals(singularNoun)) { //exceptions
163: sb.append("queries");
164: } else { //default rule appends S
165: sb.append(singularNoun).append('s');
166: }
167: } else {
168: sb.append(singularNoun); //singular form
169: }
170: return sb;
171: }
172:
173: /**
174: * Appends total working time, i.e. d h m s ms
175: * The leading space is always present.
176: *
177: * @param timeMillis time interval in millis.
178: * @param sb output buffer.
179: * @param doubleFormat format seconds.
180: * @return the modified buffer.
181: */
182: static StringBuilder appendTotalTimeDuration(final long timeMillis,
183: StringBuilder sb, Format doubleFormat) {
184: long time = timeMillis;
185: long days = time / DAY_MILLIS;
186: if (days > 0) {
187: time = time % DAY_MILLIS;
188: sb.append(' ');
189: appendPlural(sb, days, "day");
190: }
191: long hours = time / HOUR_MILLIS;
192: if (hours > 0) {
193: time = time % HOUR_MILLIS;
194: sb.append(' ');
195: appendPlural(sb, hours, "hour");
196: }
197: long minutes = time / MINUTE_MILLIS;
198: if (minutes > 0) {
199: time = time % MINUTE_MILLIS;
200: sb.append(' ');
201: appendPlural(sb, minutes, "minute");
202: }
203: double seconds = time / 1000d;
204: if (seconds > 0) {
205: sb.append(' ');
206: sb.append(doubleFormat.format(seconds)).append(" second");
207: if (seconds > 1) { //plural form
208: sb.append('s');
209: }
210: }
211: return sb;
212: }
213:
214: /**
215: * Total ETL execution time or -1 if ETL hasn't completed.
216: *
217: * @return ETL execution time in milliseconds.
218: */
219: public long getTotalTime() {
220: return finished != null && started != null ? finished.getTime()
221: - started.getTime() : -1;
222: }
223:
224: /**
225: * Returns date/time when ETL was started.
226: *
227: * @return ETL start date/time.
228: */
229: public Date getStartDate() {
230: return started == null ? null : (Date) started.clone();
231: }
232:
233: void setStarted(Date started) {
234: this .started = started;
235: }
236:
237: /**
238: * Returns date/time when ETL was completed.
239: *
240: * @return ETL finish date/time.
241: */
242: public Date getFinishDate() {
243: return finished == null ? null : (Date) finished.clone();
244: }
245:
246: void setFinished(Date finished) {
247: this .finished = finished;
248: }
249:
250: public static class ElementInfo {
251: int okCount;
252: Connection connection;
253: long statementsOnStart;
254: long statements;
255: int failedCount;
256: long started;
257: long workingTime;
258: String id;
259:
260: public int getSuccessfulExecutionCount() {
261: return okCount;
262: }
263:
264: public int getFailedExecutionCount() {
265: return failedCount;
266: }
267:
268: /**
269: * Returns total number of executed statements for this element.
270: * <p><b>Note:</b> execution in a loop affects total number,
271: * i.e. StatementsCount=loop_count*sql_statements_count
272: *
273: * @return number of executed statements.
274: */
275: public long getStatementsCount() {
276: return statements;
277: }
278:
279: /**
280: * Returns the total number of nanoseconds spent while executing this element.
281: *
282: * @return total working time in nanoseconds.
283: */
284: public long getWorkingTime() {
285: return workingTime;
286: }
287:
288: /**
289: * Returns throughput t=statements/workingTimeSeconds. The
290: * throughput has statement/second unit.
291: *
292: * @return statement/second thoughput or -1 if no statements info available or working time is zero.
293: */
294: public double getThroughput() {
295: return statements <= 0 || workingTime <= 0 ? -1
296: : 1000000000d * statements / workingTime;
297: }
298:
299: public String getId() {
300: return id;
301: }
302: }
303: }
|