/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.zip.GZIPInputStream;
import com.google_voltpatches.common.collect.Sets;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class TestVoltTrace {
private static final String FILE_NAME_PREFIX = "tracetest";
private ObjectMapper m_mapper = new ObjectMapper();
private File m_tempDir = null;
@Before
public void setUp() throws Exception {
m_tempDir = VoltFile.createTempFile(FILE_NAME_PREFIX, null);
assertTrue(m_tempDir.delete());
assertTrue(m_tempDir.mkdir());
}
@After
public void tearDown() throws Exception {
VoltTrace.closeAllAndShutdown(null, 0);
VoltFile.recursivelyDelete(m_tempDir);
m_tempDir = null;
}
@Test
public void testFileWriter() throws IOException {
final File f = VoltFile.createTempFile(FILE_NAME_PREFIX, "json");
f.deleteOnExit();
LinkedList<VoltTrace.TraceEventBatch> events = new LinkedList<>();
LinkedList<VoltTrace.TraceEvent> flatEvents = new LinkedList<>();
for (int i = 0; i < 10; i++) {
final VoltTrace.TraceEventBatch batch = new VoltTrace.TraceEventBatch(VoltTrace.Category.CI);
for (int j = 0; j < 10; j++) {
final VoltTrace.TraceEvent e = randomEvent();
batch.add(() -> e);
flatEvents.add(e);
}
events.add(batch);
}
new TraceFileWriter(f, events).run();
verifyFileContents(flatEvents, f.getAbsolutePath());
}
@Test
public void testCategoryToggle() throws IOException {
// Nothing is enabled by default
for (VoltTrace.Category category : VoltTrace.Category.values()) {
assertNull(VoltTrace.log(category));
}
// Enable two categories
VoltTrace.enableCategories(VoltTrace.Category.CI, VoltTrace.Category.SPI);
assertEquals(Sets.newHashSet(VoltTrace.Category.CI, VoltTrace.Category.SPI),
VoltTrace.enabledCategories());
for (VoltTrace.Category category : VoltTrace.Category.values()) {
if (category == VoltTrace.Category.CI || category == VoltTrace.Category.SPI) {
assertNotNull(VoltTrace.log(category));
} else {
assertNull(VoltTrace.log(category));
}
}
// Disable one of them
VoltTrace.disableCategories(VoltTrace.Category.CI);
assertEquals(Collections.singleton(VoltTrace.Category.SPI), VoltTrace.enabledCategories());
for (VoltTrace.Category category : VoltTrace.Category.values()) {
if (category == VoltTrace.Category.SPI) {
assertNotNull(VoltTrace.log(category));
} else {
assertNull(VoltTrace.log(category));
}
}
// Disable all and make sure the tracer is gone
VoltTrace.disableCategories(VoltTrace.Category.values());
assertTrue(VoltTrace.enabledCategories().isEmpty());
assertNull(VoltTrace.dump(m_tempDir.getAbsolutePath()));
}
@Test
public void testBasicTrace() throws Exception {
VoltTrace.enableCategories(VoltTrace.Category.values());
final SenderRunnable sender = new SenderRunnable();
sender.run();
final String path = VoltTrace.closeAllAndShutdown(m_tempDir.getAbsolutePath(), 0);
verifyFileContents(sender.getSentList(), path);
}
@Test
public void testTraceLimit() throws IOException {
VoltTrace.enableCategories(VoltTrace.Category.values());
// These events should be purged once the limit is reached
for (int i = 0; i < 10; i++) {
VoltTrace.log(VoltTrace.Category.CI).add(this::randomEvent);
}
// Fill the queue with exactly the limit number of events
final SenderRunnable sender = new SenderRunnable(VoltTrace.QUEUE_SIZE);
sender.run();
verifyFileContents(sender.getSentList(), VoltTrace.closeAllAndShutdown(m_tempDir.getAbsolutePath(), 0));
}
private ArrayList<VoltTrace.TraceEventType> m_allEventTypes = new ArrayList<>(EnumSet.allOf(VoltTrace.TraceEventType.class));
private Random m_random = new Random();
private VoltTrace.TraceEvent randomEvent() {
VoltTrace.TraceEvent event = null;
while (event==null) {
VoltTrace.TraceEventType type = m_allEventTypes.get(m_random.nextInt(m_allEventTypes.size()));
switch(type) {
case ASYNC_BEGIN:
event = randomAsync(true);
break;
case ASYNC_END:
event = randomAsync(false);
break;
case ASYNC_INSTANT:
event = randomInstant(true);
break;
case DURATION_BEGIN:
event = randomDurationBegin();
break;
case DURATION_END:
event = randomDurationEnd();
break;
case INSTANT:
event = randomInstant(false);
break;
case METADATA:
event = randomMeta();
break;
default:
break;
}
}
event.setTid(Thread.currentThread().getId());
event.setNanos(System.nanoTime());
return event;
}
private VoltTrace.TraceEvent randomDurationBegin() {
return new VoltTrace.TraceEvent(VoltTrace.TraceEventType.DURATION_BEGIN,
"name"+m_random.nextInt(5), null, randomArgs());
}
private VoltTrace.TraceEvent randomDurationEnd() {
return new VoltTrace.TraceEvent(VoltTrace.TraceEventType.DURATION_END,
null, null);
}
private VoltTrace.TraceEvent randomAsync(boolean begin) {
VoltTrace.TraceEventType type = (begin) ?
VoltTrace.TraceEventType.ASYNC_BEGIN : VoltTrace.TraceEventType.ASYNC_END;
return new VoltTrace.TraceEvent(type, "name"+m_random.nextInt(5),
Long.toString(m_random.nextLong()), randomArgs());
}
private VoltTrace.TraceEvent randomInstant(boolean async) {
VoltTrace.TraceEventType type = (async) ?
VoltTrace.TraceEventType.ASYNC_INSTANT : VoltTrace.TraceEventType.INSTANT;
String id = (async) ? Long.toString(m_random.nextLong()) : null;
return new VoltTrace.TraceEvent(type,
"name"+m_random.nextInt(5),
id, randomArgs());
}
private static String[] s_metadataNames = { "process_name", "process_labels", "process_sort_index",
"thread_name", "thread_sort_index"
};
private VoltTrace.TraceEvent randomMeta() {
String name = s_metadataNames[m_random.nextInt(s_metadataNames.length)];
return new VoltTrace.TraceEvent(VoltTrace.TraceEventType.METADATA, name, null,
randomArgs());
}
private static String[] s_argKeys = { "name", "dest", "ciHandle", "txnid", "commit", "key1", "keyn" };
private Object[] randomArgs() {
int count = m_random.nextInt(4);
String[] args = new String[count*2];
for (int i=0; i<count; i++) {
String key = s_argKeys[m_random.nextInt(s_argKeys.length)];
args[i*2] = key;
if (m_random.nextBoolean()) {
args[i * 2 + 1] = key + "-val";
} else {
args[i * 2 + 1] = null;
}
}
return args;
}
private void verifyFileContents(List<VoltTrace.TraceEvent> expectedList, String outfile)
throws IOException {
List<VoltTrace.TraceEvent> readEvents = new ArrayList<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(outfile))));
String line;
while ((line=reader.readLine()) != null) {
line = line.trim();
if (line.equals("]") || line.equals("[")) {
continue;
}
if (line.charAt(line.length()-1)==',') {
line = line.substring(0, line.length()-1);
}
readEvents.add(m_mapper.readValue(line, VoltTrace.TraceEvent.class));
}
reader.close();
assertEquals(expectedList.size(), readEvents.size());
readEvents.sort(Comparator.comparingDouble(VoltTrace.TraceEvent::getTs));
expectedList.sort(Comparator.comparingDouble(VoltTrace.TraceEvent::getNanos));
System.out.println("Expected");
expectedList.forEach(System.out::println);
System.out.println("Read");
readEvents.forEach(System.out::println);
for (int i = 0; i < expectedList.size(); i++) {
compare(expectedList.get(i), readEvents.get(i));
}
}
private void compare(VoltTrace.TraceEvent expected, VoltTrace.TraceEvent actual) {
assertEquals(expected.getCategory(), actual.getCategory());
assertEquals(expected.getName(), actual.getName());
assertEquals(expected.getPid(), actual.getPid());
assertEquals(expected.getTid(), actual.getTid());
assertEquals(expected.getTypeChar(), actual.getTypeChar());
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getType(), actual.getType());
assertEquals(expected.getArgs(), actual.getArgs());
}
public class SenderRunnable implements Runnable {
private final long m_count;
private List<VoltTrace.TraceEvent> m_sentList = new ArrayList<>();
public SenderRunnable() {
this(10);
}
public SenderRunnable(long count) {
m_count = count;
}
public void run() {
try {
for (int i = 0; i < m_count; i++) {
final VoltTrace.Category category = VoltTrace.Category.values()[m_random.nextInt(VoltTrace.Category.values().length)];
final VoltTrace.TraceEventBatch log = VoltTrace.log(category);
assert log != null;
VoltTrace.TraceEvent event = randomEvent();
event.setCategory(category);
String[] args = new String[event.getArgs().size()*2];
int j=0;
for (String key : event.getArgs().keySet()) {
args[j++] = key;
args[j++] = event.getArgs().get(key);
}
switch(event.getType()) {
case ASYNC_BEGIN:
log.add(() -> VoltTrace.beginAsync(event.getName(),
event.getId(),
(Object[]) args));
break;
case ASYNC_END:
log.add(() -> VoltTrace.endAsync(event.getName(),
event.getId(),
(Object[]) args));
break;
case ASYNC_INSTANT:
log.add(() -> VoltTrace.instantAsync(event.getName(),
event.getId(),
(Object[]) args));
break;
case DURATION_BEGIN:
log.add(() -> VoltTrace.beginDuration(event.getName(),
(Object[]) args));
break;
case DURATION_END:
log.add(VoltTrace::endDuration);
break;
case INSTANT:
log.add(() -> VoltTrace.instant(event.getName(),
(Object[]) args));
break;
case METADATA:
log.add(() -> VoltTrace.meta(event.getName(),
(Object[]) args));
break;
default:
throw new IllegalArgumentException("Unsupported event type: " + event.getType());
}
m_sentList.add(event);
}
} catch(Throwable t) {
t.printStackTrace();
}
}
public List<VoltTrace.TraceEvent> getSentList() {
return m_sentList;
}
}
}