/*
* Copyright 2013 Eediom Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.araqne.logdb.query.command;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.araqne.logdb.ObjectComparator;
import org.araqne.logdb.QueryCommand;
import org.araqne.logdb.QueryStopReason;
import org.araqne.logdb.Row;
import org.araqne.logdb.Strings;
import org.araqne.logdb.query.expr.Expression;
import org.araqne.logdb.sort.CloseableIterator;
import org.araqne.logdb.sort.Item;
import org.araqne.logdb.sort.ParallelMergeSorter;
public class BoxPlot extends QueryCommand {
private final int clauseCount;
private ParallelMergeSorter sorter;
private Expression expr;
private List<String> clauses;
// count per group keys
private Map<GroupKey, AtomicLong> groupCounts;
public BoxPlot(Expression expr, List<String> clauses) {
this.expr = expr;
this.clauses = clauses;
this.clauseCount = clauses.size();
}
@Override
public String getName() {
return "boxplot";
}
public Expression getExpression() {
return expr;
}
public List<String> getClauses() {
return clauses;
}
@Override
public void onStart() {
this.groupCounts = new HashMap<GroupKey, AtomicLong>();
this.sorter = new ParallelMergeSorter(new ItemComparer());
int queryId = 0;
if (getQuery() != null)
queryId = getQuery().getId();
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd_HHmmss");
sorter.setTag("_" + queryId + "_" + df.format(new Date()) + "_");
}
@Override
public void onPush(Row m) {
Object value = expr.eval(m);
if (value == null)
return;
Object[] item = new Object[clauseCount + 1];
int i = 0;
for (String clause : clauses) {
Object keyValue = m.get(clause);
if (keyValue != null)
item[i] = keyValue;
i++;
}
Object[] keys = Arrays.copyOfRange(item, 0, item.length - 1);
GroupKey groupKey = new GroupKey(keys);
AtomicLong count = groupCounts.get(groupKey);
if (count == null) {
count = new AtomicLong(1);
groupCounts.put(groupKey, count);
} else {
count.incrementAndGet();
}
item[i] = value;
try {
sorter.add(new Item(item, null));
} catch (IOException e) {
throw new IllegalStateException("cannot add sort item for query " + getQuery().getId(), e);
}
}
@Override
public void onClose(QueryStopReason reason) {
// command is not started
if (sorter == null)
return;
long rank = 0;
long iqr1Index = 0;
long iqr2Index = 0;
long iqr3Index = 0;
Object min = null;
Object iqr1 = null;
Object iqr2 = null;
Object iqr3 = null;
Object max = null;
Object last = null;
Object count = null;
GroupKey lastGroupKey = null;
CloseableIterator it;
try {
it = sorter.sort();
while (it.hasNext()) {
Item item = it.next();
Object[] values = (Object[]) item.getKey();
Object value = values[clauseCount];
if (value == null)
continue;
Object[] keys = Arrays.copyOfRange(values, 0, values.length - 1);
GroupKey groupKey = new GroupKey(keys);
if (lastGroupKey == null || !lastGroupKey.equals(groupKey)) {
if (lastGroupKey != null) {
max = last;
writeSummary(lastGroupKey, min, iqr1, iqr2, iqr3, max, count);
}
long groupSize = groupCounts.get(groupKey).get();
long quartile = groupSize / 4;
rank = 0;
iqr1Index = quartile;
iqr2Index = quartile * 2;
iqr3Index = quartile * 3;
count = groupSize;
min = value;
iqr1 = null;
iqr2 = null;
iqr3 = null;
max = null;
}
if (rank == iqr1Index)
iqr1 = value;
if (rank == iqr2Index)
iqr2 = value;
if (rank == iqr3Index)
iqr3 = value;
rank++;
last = value;
lastGroupKey = groupKey;
}
max = last;
if (lastGroupKey != null)
writeSummary(lastGroupKey, min, iqr1, iqr2, iqr3, max, count);
} catch (Throwable t) {
getQuery().cancel(t);
}
}
private void writeSummary(GroupKey groupKey, Object min, Object iqr1, Object iqr2, Object iqr3, Object max, Object count) {
Map<String, Object> summary = new HashMap<String, Object>();
int i = 0;
for (String clause : clauses)
summary.put(clause, groupKey.keys[i++]);
summary.put("min", min);
summary.put("iqr1", iqr1);
summary.put("iqr2", iqr2);
summary.put("iqr3", iqr3);
summary.put("max", max);
summary.put("count", count);
pushPipe(new Row(summary));
}
@Override
public boolean isReducer() {
return true;
}
@Override
public String toString() {
return "boxplot " + expr + " by " + Strings.join(clauses, ", ");
}
private static class ItemComparer implements Comparator<Item> {
private ObjectComparator cmp = new ObjectComparator();
@Override
public int compare(Item o1, Item o2) {
return cmp.compare(o1.getKey(), o2.getKey());
}
}
private static class GroupKey {
private Object[] keys;
public GroupKey(Object[] keys) {
this.keys = keys;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(keys);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GroupKey other = (GroupKey) obj;
if (!Arrays.equals(keys, other.keys))
return false;
return true;
}
@Override
public String toString() {
return Arrays.toString(keys);
}
}
}