package metrics4Asterisk.parse;
import metrics4Asterisk.metrics.CallMetric;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import metrics4Asterisk.util.Constants;
import org.apache.log4j.Logger;
/**
*
* The CallMapper class will parse an Asterisk queue log file and create CallMetric objects
* from the log's entries.
* @author Lance Stine
*/
public class CallMapper extends LogMapper<CallMetric> {
private static final Logger logger = Logger.getLogger(CallMapper.class);
/**
* The results from the parse
* @return
*/
private Map<String, CallMetric> map;
/**
* A reg ex pattern for call ids
*/
private Pattern pattern;
/**
* Construct an object that will map all calls in queue names provided in the validQueues argument
* @param validQueues
*/
public CallMapper(final Set<String> validQueues) {
pattern = Pattern.compile(Constants.CALLIDREGEX);
super.setValidValues(validQueues);
int i = super.getValidValues() == null ? 0 : super.getValidValues().size();
map = new HashMap<String, CallMetric>(i);
}
/**
* This method will add the following rows to a map:
* <pre>
* 1203342126|1203342117.252|superqueue|NONE|ENTERQUEUE||anonymous
* 1203342228|1203342010.239|superqueue|Local/6042@internal/n|COMPLETECALLER|88|122|2
* 1203342407|1203342299.256|superqueue|Local/6009@internal/n|CONNECT|99|1203342402.257
* 1203344442|1203344431.377|superqueue|NONE|ABANDON|5|5|2
* 1203344444|1203344256.367|superqueue|Local/6009@internal/n|COMPLETEAGENT|12|167|1
* The transfer event is not implemented for now. However its format is like the complete event.
*
* Fields in queue_log
* epoch timestamp of listed action
* uniqueid of call
* queue name
* bridged channel
* event
* event parameter 1
* event parameter 2
* event parameter 3
*
* The event parameters vary. Here is their description from the asterisk docs:
* COMPLETECALLER(holdtime|calltime|origposition)
* ABANDON(position|origposition|waittime)
* COMPLETEAGENT(holdtime|calltime|origposition)
* TRANSFER(extension,context)
* CONNECT(holdtime)
*
* There are duplicate paramters in the log. For instance hold time is in both complete and connect events.
* This application will ignore the duplicate parameters from the connect event and only use them in complete events.
*
* Any call that has an event that is not in the rowValues argument will not be completley processed. This means that talkTime
* may not be calculated. The best way to deal with this is to check for a 0 in a CallMetrick's exitQueueTime field. Discard any of
* these objects from totals or show a warning that there are uncompleted calls in the data.
*
*
* Problems in asterisk logging exist. Look at this line from my log file:
* 1194666863|1194666521.24146|superqueue||CONNECT|332|1194666839.24152
* There is no extension. This same call ID had a matching completed event without an extension as well. Always write code to
* list anomalies like this so that the accuracy of the application isn't ruined by bad log files.
*
* </pre>
* @param rowValues an array of row values
* @param logRecordTime the time in milliseconds for this row's data
*/
public void processRow(final String[] rowValues, final long logRecordTime) {
String callId = rowValues[1];
Matcher matcher = pattern.matcher(callId);
//Only process rows with a call ID.
if (matcher.matches()) {
logger.debug("matching call ID " + callId);
try {
if (!super.getValidValues().contains(rowValues[2])) {
logger.debug("skipped queue " + rowValues[2]);
return;
}
String event = rowValues[4];
if (!map.containsKey(callId)) {
if (event.equals("ENTERQUEUE")) {
CallMetric callMetric = new CallMetric();
callMetric.setEnterQueueTime(logRecordTime);
String queueName = rowValues[2];
callMetric.setQueueName(queueName);
callMetric.setCallId(callId);
logger.debug("added metric " + callMetric);
map.put(callId, callMetric);
} else {
logger.warn(callId + "|" + event + ": not in call map but something other than enter queue called");
}
} else {
if (event.equals("ENTERQUEUE")) {
logger.warn(callId + ": ENTERQUEUE event with a key already in map");
return;
}
CallMetric callMetric = map.get(callId);
if (callMetric == null) {
logger.error(callId + ": not in map but should be.");
return;
}
logger.debug(event);
if (event.equals("ABANDON")) {
String waitTime = rowValues[7];
callMetric.setWaitTime(Integer.parseInt(waitTime));
} else if (event.equals("COMPLETECALLER") || event.equals("COMPLETEAGENT")) {
String talkTime = rowValues[6];
callMetric.setTalkTime(Integer.parseInt(talkTime));
String waitTime = rowValues[5];
callMetric.setExitQueueTime(logRecordTime);
callMetric.setWaitTime(Integer.parseInt(waitTime));
} else if (event.equals("CONNECT")) {
String extension = rowValues[3];
callMetric.setExtension(extension);
String connectTime = rowValues[0];
//make the unix timestamp into miliseconds
long longconnectTime = Long.parseLong(connectTime) * 1000l;
callMetric.setEnterQueueTime(longconnectTime);
}
logger.debug("metric is " + callMetric);
}
} catch (Exception e) {
logger.error("Row ignored by exception ", e);
}
}
}
public Map<String, CallMetric> getMap() {
return map;
}
}