/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2013, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.classic.corpus;
import java.util.List;
import java.util.Random;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ClassPackagingData;
import ch.qos.logback.classic.spi.LoggerContextVO;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyVO;
/**
* Models the corpus.
*
* <p>This contains the probability distributions of levels, logger names,
* messages, message arguments.
*
* @author Ceki Gülcü
*
*/
public class CorpusModel {
// N(u,s) denotes a random variable normally distributed with mean u and
// variance sqrt(s), where sqrt() is the square root function. For an
// explanation of normal distribution please see
// http://en.wikipedia.org/wiki/Normal_distribution
// It is assumed that the number of parts in a logger name is a random
// variable normally distributed with mean AVERAGE_LOGGER_NAME_PARTS and
// standard deviation STD_DEV_FOR_LOGGER_NAME_PARTS
static final int AVERAGE_LOGGER_NAME_PARTS = 6;
static final int STD_DEV_FOR_LOGGER_NAME_PARTS = 3;
// It is assumed that there are LOGGER_POOL_SIZE logger names
// in our model corpus.
static final int LOGGER_POOL_SIZE = 1000;
// It is assumed that there are LOG_STATEMENT_POOL_SIZE log statements
// in our model corpus.
static final int LOG_STATEMENT_POOL_SIZE = LOGGER_POOL_SIZE * 8;
// level distribution is determined by the following table
// It corresponds to TRACE 3%, DEBUG 30%, INFO 30%, WARN 5%,
// ERROR 5%. See also getRandomLevel() method.
static final double[] LEVEL_DISTRIBUTION = new double[] { .3, .3, .9, .95 };
// It is assumed that the number of words in the message (contained in a log
// statement) is a random variable normally distributed with mean
// AVERAGE_MESSAGE_WORDS and standard deviation STD_DEV_FOR_MESSAGE_WORDS
static final int AVERAGE_MESSAGE_WORDS = 8;
static final int STD_DEV_FOR_MESSAGE_WORDS = 4;
// messages will have no arguments 80% of the time, one argument in 8%, two
// arguments in 7% and three arguments in 5% of cases
static final double[] ARGUMENT_DISTRIBUTION = new double[] { .80, .88, 0.95 };
static final double THROWABLE_PROPABILITY_FOR_WARNING = .1;
static final double THROWABLE_PROPABILITY_FOR_ERRORS = .3;
// .5 of throwables are nested once
static final double NESTING_PROBABILITY = .5;
// For each logging event the timer is incremented by a certain value. it is
// assumed that this value is a random variable normally distributed with mean
// AVERAGE_MILLIS_INCREMENT and standard deviation
// STD_DEV_FOR_MILLIS_INCREMENT
static final int AVERAGE_MILLIS_INCREMENT = 10;
static final int STD_DEV_FOR_MILLIS_INCREMENT = 5;
// assume that there are THREAD_POOL_SIZE threads in the corpus
static final int THREAD_POOL_SIZE = 10;
final Random random;
final List<String> worldList;
String[] threadNamePool;
LogStatement[] logStatementPool;
String[] loggerNamePool;
// 2009-03-06 13:08 GMT
long lastTimeStamp = 1236344888578L;
public CorpusModel(long seed, List<String> worldList) {
random = new Random(seed);
this.worldList = worldList;
buildThreadNamePool();
buildLoggerNamePool();
buildLogStatementPool();
}
private void buildThreadNamePool() {
threadNamePool = new String[THREAD_POOL_SIZE];
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
threadNamePool[i] = "CorpusMakerThread-" + i;
}
}
private void buildLoggerNamePool() {
loggerNamePool = new String[LOGGER_POOL_SIZE];
for (int i = 0; i < LOGGER_POOL_SIZE; i++) {
loggerNamePool[i] = makeRandomLoggerName();
}
}
private void buildLogStatementPool() {
logStatementPool = new LogStatement[LOG_STATEMENT_POOL_SIZE];
for (int i = 0; i < LOG_STATEMENT_POOL_SIZE; i++) {
logStatementPool[i] = makeRandomLogStatement(loggerNamePool);
}
}
private int[] getRandomAnchorPositions(int wordCount, int numAnchors) {
// note that the same position may appear multiple times in
// positionsIndex, but without serious consequences
int[] positionsIndex = new int[numAnchors];
for (int i = 0; i < numAnchors; i++) {
positionsIndex[i] = random.nextInt(wordCount);
}
return positionsIndex;
}
private String[] getRandomWords(int n) {
String[] wordArray = new String[n];
for (int i = 0; i < n; i++) {
wordArray[i] = getRandomWord();
}
return wordArray;
}
public long getRandomLong() {
return random.nextLong();
}
public String getRandomThreadNameFromPool() {
int index = random.nextInt(THREAD_POOL_SIZE);
return threadNamePool[index];
}
public LogStatement getRandomLogStatementFromPool() {
int index = random.nextInt(logStatementPool.length);
return logStatementPool[index];
}
private String getRandomLoggerNameFromPool(String[] loggerNamePool) {
int index = random.nextInt(loggerNamePool.length);
return loggerNamePool[index];
}
public long getRandomTimeStamp() {
// subtract 1 so that 0 is allowed
lastTimeStamp += RandomUtil.gaussianAsPositiveInt(random,
AVERAGE_MILLIS_INCREMENT, STD_DEV_FOR_MILLIS_INCREMENT) - 1;
return lastTimeStamp;
}
LoggerContextVO getRandomlyNamedLoggerContextVO() {
LoggerContext lc = new LoggerContext();
lc.setName(getRandomJavaIdentifier());
return new LoggerContextVO(lc);
}
String getRandomWord() {
int size = worldList.size();
int randomIndex = random.nextInt(size);
return worldList.get(randomIndex);
}
String extractLastPart(String loggerName) {
int i = loggerName.lastIndexOf('.');
if (i == -1) {
return loggerName;
} else {
return loggerName.substring(i + 1);
}
}
public StackTraceElement[] getRandomCallerData(int depth, String loggerName) {
StackTraceElement[] cda = new StackTraceElement[depth];
StackTraceElement cd = new StackTraceElement(loggerName,
getRandomJavaIdentifier(), extractLastPart(loggerName), 0);
cda[0] = cd;
for (int i = 1; i < depth; i++) {
String ln = getRandomLoggerNameFromPool(loggerNamePool);
cda[i] = new StackTraceElement(ln, getRandomJavaIdentifier(),
extractLastPart(ln), i * 10);
}
return cda;
}
public Object[] getRandomArgumentArray(int numOfArguments) {
if (numOfArguments == 0) {
return null;
}
Object[] argumentArray = new Object[numOfArguments];
for (int i = 0; i < numOfArguments; i++) {
argumentArray[i] = new Long(random.nextLong());
}
return argumentArray;
}
private MessageArgumentTuple makeRandomMessageArgumentTuple() {
int numOfArguments = getNumberOfMessageArguments();
int wordCount = RandomUtil.gaussianAsPositiveInt(random,
AVERAGE_MESSAGE_WORDS, STD_DEV_FOR_MESSAGE_WORDS);
String[] wordArray = getRandomWords(wordCount);
int[] anchorPositions = getRandomAnchorPositions(wordCount, numOfArguments);
for (int anchorIndex : anchorPositions) {
wordArray[anchorIndex] = "{}";
}
StringBuilder sb = new StringBuilder();
for (int i = 1; i < wordCount; i++) {
sb.append(wordArray[i]).append(' ');
}
sb.append(getRandomWord());
return new MessageArgumentTuple(sb.toString(), numOfArguments);
}
private LogStatement makeRandomLogStatement(String[] loggerNamePool) {
MessageArgumentTuple mat = makeRandomMessageArgumentTuple();
String loggerName = getRandomLoggerNameFromPool(loggerNamePool);
Level randomLevel = getRandomLevel();
Throwable t = getRandomThrowable(randomLevel);
ThrowableProxyVO throwableProxy = null;
if (t != null) {
throwableProxy = ThrowableProxyVO.build(new ThrowableProxy(t));
pupulateWithPackagingData(throwableProxy.getStackTraceElementProxyArray());
}
return new LogStatement(loggerName, randomLevel, mat,
throwableProxy);
}
private Throwable getRandomThrowable(Level level) {
double rn = random.nextDouble();
if ((level == Level.WARN && rn < THROWABLE_PROPABILITY_FOR_WARNING)
|| (level == Level.ERROR && rn < THROWABLE_PROPABILITY_FOR_ERRORS)) {
return ExceptionBuilder.build(random, NESTING_PROBABILITY);
} else {
return null;
}
}
private void pupulateWithPackagingData(StackTraceElementProxy[] stepArray) {
int i = 0;
for (StackTraceElementProxy step : stepArray) {
String identifier = "na";
String version = "na";
if (i++ % 2 == 0) {
identifier = getRandomJavaIdentifier();
version = getRandomJavaIdentifier();
}
ClassPackagingData cpd = new ClassPackagingData(identifier, version);
step.setClassPackagingData(cpd);
}
}
private int getNumberOfMessageArguments() {
double rn = random.nextDouble();
if (rn < ARGUMENT_DISTRIBUTION[0]) {
return 0;
}
if (rn < ARGUMENT_DISTRIBUTION[1]) {
return 1;
}
if (rn < ARGUMENT_DISTRIBUTION[2]) {
return 2;
}
return 3;
}
String getRandomJavaIdentifier() {
String w = getRandomWord();
w = w.replaceAll("\\p{Punct}", "");
return w;
}
private String makeRandomLoggerName() {
int parts = RandomUtil.gaussianAsPositiveInt(random,
AVERAGE_LOGGER_NAME_PARTS, STD_DEV_FOR_LOGGER_NAME_PARTS);
StringBuilder sb = new StringBuilder();
for (int i = 1; i < parts; i++) {
sb.append(getRandomJavaIdentifier()).append('.');
}
sb.append(getRandomJavaIdentifier());
return sb.toString();
}
private Level getRandomLevel() {
double rn = random.nextDouble();
if (rn < LEVEL_DISTRIBUTION[0]) {
return Level.TRACE;
}
if (rn < LEVEL_DISTRIBUTION[1]) {
return Level.DEBUG;
}
if (rn < LEVEL_DISTRIBUTION[2]) {
return Level.INFO;
}
if (rn < LEVEL_DISTRIBUTION[3]) {
return Level.WARN;
}
return Level.ERROR;
}
}