001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.corpus;
015
016import java.util.List;
017import java.util.Random;
018
019import ch.qos.logback.classic.Level;
020import ch.qos.logback.classic.LoggerContext;
021import ch.qos.logback.classic.spi.ClassPackagingData;
022import ch.qos.logback.classic.spi.LoggerContextVO;
023import ch.qos.logback.classic.spi.StackTraceElementProxy;
024import ch.qos.logback.classic.spi.ThrowableProxy;
025import ch.qos.logback.classic.spi.ThrowableProxyVO;
026
027/**
028 * Models the corpus.
029 * 
030 * <p>This contains the probability distributions of levels, logger names,
031 * messages, message arguments.
032 * 
033 * @author Ceki G&uuml;lc&uuml;
034 * 
035 */
036public class CorpusModel {
037
038    // N(u,s) denotes a random variable normally distributed with mean u and
039    // variance sqrt(s), where sqrt() is the square root function. For an
040    // explanation of normal distribution please see
041    // http://en.wikipedia.org/wiki/Normal_distribution
042
043    // It is assumed that the number of parts in a logger name is a random
044    // variable normally distributed with mean AVERAGE_LOGGER_NAME_PARTS and
045    // standard deviation STD_DEV_FOR_LOGGER_NAME_PARTS
046    static final int AVERAGE_LOGGER_NAME_PARTS = 6;
047    static final int STD_DEV_FOR_LOGGER_NAME_PARTS = 3;
048
049    // It is assumed that there are LOGGER_POOL_SIZE logger names
050    // in our model corpus.
051    static final int LOGGER_POOL_SIZE = 1000;
052
053    // It is assumed that there are LOG_STATEMENT_POOL_SIZE log statements
054    // in our model corpus.
055    static final int LOG_STATEMENT_POOL_SIZE = LOGGER_POOL_SIZE * 8;
056
057    // level distribution is determined by the following table
058    // It corresponds to TRACE 3%, DEBUG 30%, INFO 30%, WARN 5%,
059    // ERROR 5%. See also getRandomLevel() method.
060    static final double[] LEVEL_DISTRIBUTION = new double[] { .3, .3, .9, .95 };
061
062    // It is assumed that the number of words in the message (contained in a log
063    // statement) is a random variable normally distributed with mean
064    // AVERAGE_MESSAGE_WORDS and standard deviation STD_DEV_FOR_MESSAGE_WORDS
065    static final int AVERAGE_MESSAGE_WORDS = 8;
066    static final int STD_DEV_FOR_MESSAGE_WORDS = 4;
067
068    // messages will have no arguments 80% of the time, one argument in 8%, two
069    // arguments in 7% and three arguments in 5% of cases
070    static final double[] ARGUMENT_DISTRIBUTION = new double[] { .80, .88, 0.95 };
071
072    static final double THROWABLE_PROPABILITY_FOR_WARNING = .1;
073    static final double THROWABLE_PROPABILITY_FOR_ERRORS = .3;
074    // .5 of throwables are nested once
075    static final double NESTING_PROBABILITY = .5;
076
077    // For each logging event the timer is incremented by a certain value. it is
078    // assumed that this value is a random variable normally distributed with mean
079    // AVERAGE_MILLIS_INCREMENT and standard deviation
080    // STD_DEV_FOR_MILLIS_INCREMENT
081    static final int AVERAGE_MILLIS_INCREMENT = 10;
082    static final int STD_DEV_FOR_MILLIS_INCREMENT = 5;
083
084    // assume that there are THREAD_POOL_SIZE threads in the corpus
085    static final int THREAD_POOL_SIZE = 10;
086
087    final Random random;
088    final List<String> worldList;
089    String[] threadNamePool;
090    LogStatement[] logStatementPool;
091    String[] loggerNamePool;
092
093    // 2009-03-06 13:08 GMT
094    long lastTimeStamp = 1236344888578L;
095
096    public CorpusModel(long seed, List<String> worldList) {
097        random = new Random(seed);
098        this.worldList = worldList;
099        buildThreadNamePool();
100        buildLoggerNamePool();
101        buildLogStatementPool();
102    }
103
104    private void buildThreadNamePool() {
105        threadNamePool = new String[THREAD_POOL_SIZE];
106        for (int i = 0; i < THREAD_POOL_SIZE; i++) {
107            threadNamePool[i] = "CorpusMakerThread-" + i;
108        }
109    }
110
111    private void buildLoggerNamePool() {
112        loggerNamePool = new String[LOGGER_POOL_SIZE];
113        for (int i = 0; i < LOGGER_POOL_SIZE; i++) {
114            loggerNamePool[i] = makeRandomLoggerName();
115        }
116    }
117
118    private void buildLogStatementPool() {
119        logStatementPool = new LogStatement[LOG_STATEMENT_POOL_SIZE];
120        for (int i = 0; i < LOG_STATEMENT_POOL_SIZE; i++) {
121            logStatementPool[i] = makeRandomLogStatement(loggerNamePool);
122        }
123    }
124
125    private int[] getRandomAnchorPositions(int wordCount, int numAnchors) {
126        // note that the same position may appear multiple times in
127        // positionsIndex, but without serious consequences
128        int[] positionsIndex = new int[numAnchors];
129        for (int i = 0; i < numAnchors; i++) {
130            positionsIndex[i] = random.nextInt(wordCount);
131        }
132        return positionsIndex;
133    }
134
135    private String[] getRandomWords(int n) {
136        String[] wordArray = new String[n];
137        for (int i = 0; i < n; i++) {
138            wordArray[i] = getRandomWord();
139        }
140        return wordArray;
141    }
142
143    public long getRandomLong() {
144        return random.nextLong();
145    }
146
147    public String getRandomThreadNameFromPool() {
148        int index = random.nextInt(THREAD_POOL_SIZE);
149        return threadNamePool[index];
150    }
151
152    public LogStatement getRandomLogStatementFromPool() {
153        int index = random.nextInt(logStatementPool.length);
154        return logStatementPool[index];
155    }
156
157    private String getRandomLoggerNameFromPool(String[] loggerNamePool) {
158        int index = random.nextInt(loggerNamePool.length);
159        return loggerNamePool[index];
160    }
161
162    public long getRandomTimeStamp() {
163        // subtract 1 so that 0 is allowed
164        lastTimeStamp += RandomUtil.gaussianAsPositiveInt(random, AVERAGE_MILLIS_INCREMENT, STD_DEV_FOR_MILLIS_INCREMENT) - 1;
165        return lastTimeStamp;
166    }
167
168    LoggerContextVO getRandomlyNamedLoggerContextVO() {
169        LoggerContext lc = new LoggerContext();
170        lc.setName(getRandomJavaIdentifier());
171        return new LoggerContextVO(lc);
172    }
173
174    String getRandomWord() {
175        int size = worldList.size();
176        int randomIndex = random.nextInt(size);
177        return worldList.get(randomIndex);
178    }
179
180    String extractLastPart(String loggerName) {
181        int i = loggerName.lastIndexOf('.');
182        if (i == -1) {
183            return loggerName;
184        } else {
185            return loggerName.substring(i + 1);
186        }
187    }
188
189    public StackTraceElement[] getRandomCallerData(int depth, String loggerName) {
190        StackTraceElement[] cda = new StackTraceElement[depth];
191        StackTraceElement cd = new StackTraceElement(loggerName, getRandomJavaIdentifier(), extractLastPart(loggerName), 0);
192        cda[0] = cd;
193        for (int i = 1; i < depth; i++) {
194            String ln = getRandomLoggerNameFromPool(loggerNamePool);
195            cda[i] = new StackTraceElement(ln, getRandomJavaIdentifier(), extractLastPart(ln), i * 10);
196        }
197        return cda;
198    }
199
200    public Object[] getRandomArgumentArray(int numOfArguments) {
201        if (numOfArguments == 0) {
202            return null;
203        }
204        Object[] argumentArray = new Object[numOfArguments];
205        for (int i = 0; i < numOfArguments; i++) {
206            argumentArray[i] = Long.valueOf(random.nextLong());
207        }
208        return argumentArray;
209    }
210
211    private MessageArgumentTuple makeRandomMessageArgumentTuple() {
212        int numOfArguments = getNumberOfMessageArguments();
213
214        int wordCount = RandomUtil.gaussianAsPositiveInt(random, AVERAGE_MESSAGE_WORDS, STD_DEV_FOR_MESSAGE_WORDS);
215        String[] wordArray = getRandomWords(wordCount);
216
217        int[] anchorPositions = getRandomAnchorPositions(wordCount, numOfArguments);
218
219        for (int anchorIndex : anchorPositions) {
220            wordArray[anchorIndex] = "{}";
221        }
222
223        StringBuilder sb = new StringBuilder();
224        for (int i = 1; i < wordCount; i++) {
225            sb.append(wordArray[i]).append(' ');
226        }
227        sb.append(getRandomWord());
228        return new MessageArgumentTuple(sb.toString(), numOfArguments);
229    }
230
231    private LogStatement makeRandomLogStatement(String[] loggerNamePool) {
232        MessageArgumentTuple mat = makeRandomMessageArgumentTuple();
233        String loggerName = getRandomLoggerNameFromPool(loggerNamePool);
234        Level randomLevel = getRandomLevel();
235        Throwable t = getRandomThrowable(randomLevel);
236        ThrowableProxyVO throwableProxy = null;
237        if (t != null) {
238            throwableProxy = ThrowableProxyVO.build(new ThrowableProxy(t));
239            pupulateWithPackagingData(throwableProxy.getStackTraceElementProxyArray());
240        }
241        return new LogStatement(loggerName, randomLevel, mat, throwableProxy);
242    }
243
244    private Throwable getRandomThrowable(Level level) {
245        double rn = random.nextDouble();
246        if ((level == Level.WARN && rn < THROWABLE_PROPABILITY_FOR_WARNING) || (level == Level.ERROR && rn < THROWABLE_PROPABILITY_FOR_ERRORS)) {
247            return ExceptionBuilder.build(random, NESTING_PROBABILITY);
248        } else {
249            return null;
250        }
251    }
252
253    private void pupulateWithPackagingData(StackTraceElementProxy[] stepArray) {
254        int i = 0;
255        for (StackTraceElementProxy step : stepArray) {
256            String identifier = "na";
257            String version = "na";
258            if (i++ % 2 == 0) {
259                identifier = getRandomJavaIdentifier();
260                version = getRandomJavaIdentifier();
261            }
262            ClassPackagingData cpd = new ClassPackagingData(identifier, version);
263            step.setClassPackagingData(cpd);
264        }
265    }
266
267    private int getNumberOfMessageArguments() {
268        double rn = random.nextDouble();
269        if (rn < ARGUMENT_DISTRIBUTION[0]) {
270            return 0;
271        }
272        if (rn < ARGUMENT_DISTRIBUTION[1]) {
273            return 1;
274        }
275        if (rn < ARGUMENT_DISTRIBUTION[2]) {
276            return 2;
277        }
278        return 3;
279    }
280
281    String getRandomJavaIdentifier() {
282        String w = getRandomWord();
283        w = w.replaceAll("\\p{Punct}", "");
284        return w;
285    }
286
287    private String makeRandomLoggerName() {
288        int parts = RandomUtil.gaussianAsPositiveInt(random, AVERAGE_LOGGER_NAME_PARTS, STD_DEV_FOR_LOGGER_NAME_PARTS);
289        StringBuilder sb = new StringBuilder();
290        for (int i = 1; i < parts; i++) {
291            sb.append(getRandomJavaIdentifier()).append('.');
292        }
293        sb.append(getRandomJavaIdentifier());
294        return sb.toString();
295    }
296
297    private Level getRandomLevel() {
298        double rn = random.nextDouble();
299        if (rn < LEVEL_DISTRIBUTION[0]) {
300            return Level.TRACE;
301        }
302        if (rn < LEVEL_DISTRIBUTION[1]) {
303            return Level.DEBUG;
304        }
305
306        if (rn < LEVEL_DISTRIBUTION[2]) {
307            return Level.INFO;
308        }
309
310        if (rn < LEVEL_DISTRIBUTION[3]) {
311            return Level.WARN;
312        }
313
314        return Level.ERROR;
315    }
316}