IGNITE-13888 Provide the utility to output performance statistics operations to the console (#40)
diff --git a/modules/performance-statistics-ext/bin/print-statistics.sh b/modules/performance-statistics-ext/bin/print-statistics.sh
new file mode 100644
index 0000000..4d250a1
--- /dev/null
+++ b/modules/performance-statistics-ext/bin/print-statistics.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+#
+# The script is used to read performance statistics files to the console or file.
+#
+
+SCRIPT_DIR=$(cd $(dirname "$0"); pwd)
+
+#
+# JVM options. See http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp for more details.
+#
+# ADD YOUR/CHANGE ADDITIONAL OPTIONS HERE
+#
+JVM_OPTS="-Xms32m -Xmx512m"
+
+#
+# Define classpath
+#
+CP="${SCRIPT_DIR}/libs/*"
+
+#
+# Set main class to run the tool.
+#
+MAIN_CLASS=org.apache.ignite.internal.performancestatistics.PerformanceStatisticsPrinter
+
+#
+# Garbage Collection options.
+#
+JVM_OPTS="\
+ -XX:+UseG1GC \
+ ${JVM_OPTS}"
+
+#
+# Run tool.
+#
+java ${JVM_OPTS} -cp "${CP}" ${MAIN_CLASS} $@
diff --git a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsPrinter.java b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsPrinter.java
new file mode 100644
index 0000000..789bf5b
--- /dev/null
+++ b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsPrinter.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.internal.performancestatistics;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.ignite.internal.performancestatistics.handlers.PrintHandler;
+import org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsReader;
+import org.apache.ignite.internal.processors.performancestatistics.OperationType;
+import org.apache.ignite.internal.util.typedef.internal.A;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.Nullable;
+
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.joining;
+
+/**
+ * Performance statistics printer.
+ */
+public class PerformanceStatisticsPrinter {
+ /**
+ * @param args Program arguments or '-h' to get usage help.
+ */
+ public static void main(String... args) throws Exception {
+ Parameters params = parseArguments(args);
+
+ validateParameters(params);
+
+ PrintStream ps = printStream(params.outFile);
+
+ try {
+ new FilePerformanceStatisticsReader(
+ new PrintHandler(ps, params.ops, params.from, params.to,params.cacheIds))
+ .read(singletonList(new File(params.statFileOrDir)));
+ }
+ finally {
+ if (params.outFile != null)
+ ps.close();
+ }
+ }
+
+ /**
+ * Parses arguments or print help.
+ *
+ * @param args Arguments to parse.
+ * @return Program arguments.
+ */
+ private static Parameters parseArguments(String[] args) {
+ if (args == null || args.length == 0 || "--help".equalsIgnoreCase(args[0]) || "-h".equalsIgnoreCase(args[0]))
+ printHelp();
+
+ Parameters params = new Parameters();
+
+ Iterator<String> iter = Arrays.asList(args).iterator();
+
+ params.statFileOrDir = iter.next();
+
+ while (iter.hasNext()) {
+ String arg = iter.next();
+
+ if ("--out".equalsIgnoreCase(arg)) {
+ A.ensure(iter.hasNext(), "Expected output file name");
+
+ params.outFile = iter.next();
+ }
+ else if ("--ops".equalsIgnoreCase(arg)) {
+ A.ensure(iter.hasNext(), "Expected operation types");
+
+ String[] ops = iter.next().split(",");
+
+ A.ensure(ops.length > 0, "Expected at least one operation");
+
+ params.ops = new BitSet();
+
+ for (String op : ops) {
+ OperationType opType = enumIgnoreCase(op, OperationType.class);
+
+ A.ensure(opType != null, "Unknown operation type [op=" + op + ']');
+
+ params.ops.set(opType.id());
+ }
+ }
+ else if ("--from".equalsIgnoreCase(arg)) {
+ A.ensure(iter.hasNext(), "Expected time from");
+
+ params.from = Long.parseLong(iter.next());
+ }
+ else if ("--to".equalsIgnoreCase(arg)) {
+ A.ensure(iter.hasNext(), "Expected time to");
+
+ params.to = Long.parseLong(iter.next());
+ }
+ else if ("--caches".equalsIgnoreCase(arg)) {
+ A.ensure(iter.hasNext(), "Expected cache names");
+
+ String[] caches = iter.next().split(",");
+
+ A.ensure(caches.length > 0, "Expected at least one cache name");
+
+ params.cacheIds = Arrays.stream(caches).map(CU::cacheId).collect(Collectors.toSet());
+ }
+ else
+ throw new IllegalArgumentException("Unknown command: " + arg);
+ }
+
+ return params;
+ }
+
+ /** Prints help. */
+ private static void printHelp() {
+ String ops = Arrays.stream(OperationType.values()).map(Enum::toString).collect(joining(", "));
+
+ System.out.println("The script is used to print performance statistics files to the console or file." +
+ U.nl() + U.nl() +
+ "Usage: print-statistics.sh path_to_files [--out out_file] [--ops op_types] " +
+ "[--from startTimeFrom] [--to startTimeTo] [--caches cache_names]" + U.nl() +
+ U.nl() +
+ " path_to_files - Performance statistics file or files directory." + U.nl() +
+ " out_file - Output file." + U.nl() +
+ " op_types - Comma separated list of operation types to filter the output." + U.nl() +
+ " from - The minimum operation start time to filter the output." + U.nl() +
+ " to - The maximum operation start time to filter the output." + U.nl() +
+ " cache_names - Comma separated list of cache names to filter the output." + U.nl() +
+ U.nl() +
+ "Times must be specified in the Unix time format in milliseconds." + U.nl() +
+ "List of operation types: " + ops + '.');
+
+ System.exit(0);
+ }
+
+ /** @param params Validates parameters. */
+ private static void validateParameters(Parameters params) {
+ File statFileOrDir = new File(params.statFileOrDir);
+
+ A.ensure(statFileOrDir.exists(), "Performance statistics file or files directory does not exists");
+ }
+
+ /** @return Print stream to the console or file. */
+ private static PrintStream printStream(@Nullable String outFile) {
+ PrintStream ps;
+
+ if (outFile != null) {
+ try {
+ ps = new PrintStream(new BufferedOutputStream(new FileOutputStream(new File(outFile), true)));
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException("Cannot write to output file", e);
+ }
+ }
+ else
+ ps = System.out;
+
+ return ps;
+ }
+
+ /**
+ * Gets the enum for the given name ignore case.
+ *
+ * @param name Enum name.
+ * @param cls Enum class.
+ * @return The enum or {@code null} if not found.
+ */
+ private static <E extends Enum<E>> @Nullable E enumIgnoreCase(String name, Class<E> cls) {
+ for (E e : cls.getEnumConstants()) {
+ if (e.name().equalsIgnoreCase(name))
+ return e;
+ }
+
+ return null;
+ }
+
+ /** Printer parameters. */
+ private static class Parameters {
+ /** Performance statistics file or files directory. */
+ private String statFileOrDir;
+
+ /** Output file. */
+ @Nullable private String outFile;
+
+ /** Operation types to print. */
+ @Nullable private BitSet ops;
+
+ /** The minimum operation start time to filter the output. */
+ private long from = Long.MIN_VALUE;
+
+ /** The maximum operation start time to filter the output. */
+ private long to = Long.MAX_VALUE;
+
+ /** Cache identifiers to filter the output. */
+ @Nullable private Set<Integer> cacheIds;
+ }
+}
diff --git a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/handlers/PrintHandler.java b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/handlers/PrintHandler.java
new file mode 100644
index 0000000..df27ae8
--- /dev/null
+++ b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/handlers/PrintHandler.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.internal.performancestatistics.handlers;
+
+import java.io.PrintStream;
+import java.util.BitSet;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
+import org.apache.ignite.internal.processors.performancestatistics.OperationType;
+import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsHandler;
+import org.apache.ignite.internal.util.GridIntIterator;
+import org.apache.ignite.internal.util.GridIntList;
+import org.apache.ignite.lang.IgniteUuid;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.performancestatistics.util.Utils.printEscaped;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_START;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.JOB;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY_READS;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TASK;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TX_COMMIT;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TX_ROLLBACK;
+
+/**
+ * Handler to print performance statistics operations.
+ */
+public class PrintHandler implements PerformanceStatisticsHandler {
+ /** Print stream. */
+ private final PrintStream ps;
+
+ /** Operation types. */
+ @Nullable private final BitSet ops;
+
+ /** Start time from. */
+ private final long from;
+
+ /** Start time to. */
+ private final long to;
+
+ /** Cache identifiers to filter the output. */
+ @Nullable private final Set<Integer> cacheIds;
+
+ /**
+ * @param ps Print stream.
+ * @param ops Set of operations to print.
+ * @param from The minimum operation start time to filter the output.
+ * @param to The maximum operation start time to filter the output.
+ * @param cacheIds Cache identifiers to filter the output.
+ */
+ public PrintHandler(PrintStream ps, @Nullable BitSet ops, long from, long to, @Nullable Set<Integer> cacheIds) {
+ this.ps = ps;
+ this.ops = ops;
+ this.from = from;
+ this.to = to;
+ this.cacheIds = cacheIds;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void cacheStart(UUID nodeId, int cacheId, String name) {
+ if (skip(CACHE_START, cacheId))
+ return;
+
+ ps.print("{\"op\":\"" + CACHE_START);
+ ps.print("\",\"nodeId\":\"");
+ ps.print(nodeId);
+ ps.print("\",\"cacheId\":");
+ ps.print(cacheId);
+ ps.print(",\"name\":\"");
+ printEscaped(ps, name);
+ ps.println("\"}");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void cacheOperation(UUID nodeId, OperationType type, int cacheId, long startTime, long duration) {
+ if (skip(type, startTime, cacheId))
+ return;
+
+ ps.print("{\"op\":\"");
+ ps.print(type);
+ ps.print("\",\"nodeId\":\"");
+ ps.print(nodeId);
+ ps.print("\",\"cacheId\":");
+ ps.print(cacheId);
+ ps.print(",\"startTime\":");
+ ps.print(startTime);
+ ps.print(",\"duration\":");
+ ps.print(duration);
+ ps.println("}");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void transaction(UUID nodeId, GridIntList cacheIds, long startTime, long duration,
+ boolean commited) {
+ OperationType op = commited ? TX_COMMIT : TX_ROLLBACK;
+
+ if (skip(op, startTime, cacheIds))
+ return;
+
+ ps.print("{\"op\":\"");
+ ps.print(op);
+ ps.print("\",\"nodeId\":\"");
+ ps.print(nodeId);
+ ps.print("\",\"cacheIds\":");
+ ps.print(cacheIds);
+ ps.print(",\"startTime\":");
+ ps.print(startTime);
+ ps.print(",\"duration\":");
+ ps.print(duration);
+ ps.println("}");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void query(UUID nodeId, GridCacheQueryType type, String text, long id, long startTime,
+ long duration, boolean success) {
+ if (skip(QUERY, startTime))
+ return;
+
+ ps.print("{\"op\":\"" + QUERY);
+ ps.print("\",\"nodeId\":\"");
+ ps.print(nodeId);
+ ps.print("\",\"type\":\"");
+ ps.print(type);
+ ps.print("\",\"text\":\"");
+ printEscaped(ps, text);
+ ps.print("\",\"id\":");
+ ps.print(id);
+ ps.print(",\"startTime\":");
+ ps.print(startTime);
+ ps.print(",\"duration\":");
+ ps.print(duration);
+ ps.print(",\"success\":");
+ ps.print(success);
+ ps.println("}");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void queryReads(UUID nodeId, GridCacheQueryType type, UUID queryNodeId, long id,
+ long logicalReads, long physicalReads) {
+ if (skip(QUERY_READS))
+ return;
+
+ ps.print("{\"op\":\"" + QUERY_READS);
+ ps.print("\",\"nodeId\":\"");
+ ps.print(nodeId);
+ ps.print("\",\"type\":\"");
+ ps.print(type);
+ ps.print("\",\"queryNodeId\":\"");
+ ps.print(queryNodeId);
+ ps.print("\",\"id\":");
+ ps.print(id);
+ ps.print(",\"logicalReads\":");
+ ps.print(logicalReads);
+ ps.print(",\"physicalReads\":");
+ ps.print(physicalReads);
+ ps.println("}");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void task(UUID nodeId, IgniteUuid sesId, String taskName, long startTime, long duration,
+ int affPartId) {
+ if (skip(TASK, startTime))
+ return;
+
+ ps.print("{\"op\":\"" + TASK);
+ ps.print("\",\"nodeId\":\"");
+ ps.print(nodeId);
+ ps.print("\",\"sesId\":\"");
+ ps.print(sesId);
+ ps.print("\",\"taskName\":\"");
+ printEscaped(ps, taskName);
+ ps.print("\",\"startTime\":");
+ ps.print(startTime);
+ ps.print(",\"duration\":");
+ ps.print(duration);
+ ps.print(",\"affPartId\":");
+ ps.print(affPartId);
+ ps.println("}");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void job(UUID nodeId, IgniteUuid sesId, long queuedTime, long startTime, long duration,
+ boolean timedOut) {
+ if (skip(JOB, startTime))
+ return;
+
+ ps.print("{\"op\":\"" + JOB);
+ ps.print("\",\"nodeId\":\"");
+ ps.print(nodeId);
+ ps.print("\",\"sesId\":\"");
+ ps.print(sesId);
+ ps.print("\",\"queuedTime\":");
+ ps.print(queuedTime);
+ ps.print(",\"startTime\":");
+ ps.print(startTime);
+ ps.print(",\"duration\":");
+ ps.print(duration);
+ ps.print(",\"timedOut\":");
+ ps.print(timedOut);
+ ps.println("}");
+ }
+
+ /** @return {@code True} if the operation should be skipped. */
+ private boolean skip(OperationType op) {
+ return !(ops == null || ops.get(op.id()));
+ }
+
+ /** @return {@code True} if the operation should be skipped. */
+ private boolean skip(OperationType op, long startTime) {
+ return skip(op) || startTime < from || startTime > to;
+ }
+
+ /** @return {@code True} if the operation should be skipped. */
+ private boolean skip(OperationType op, int cacheId) {
+ return skip(op) || !(cacheIds == null || cacheIds.contains(cacheId));
+ }
+
+ /** @return {@code True} if the operation should be skipped. */
+ private boolean skip(OperationType op, long startTime, int cacheId) {
+ return skip(op, startTime) || !(cacheIds == null || cacheIds.contains(cacheId));
+ }
+
+ /** @return {@code True} if the operation should be skipped. */
+ private boolean skip(OperationType op, long startTime, GridIntList cacheIds) {
+ if (skip(op, startTime))
+ return true;
+
+ if (this.cacheIds == null)
+ return false;
+
+ GridIntIterator iter = cacheIds.iterator();
+
+ while (iter.hasNext()) {
+ if (this.cacheIds.contains(iter.next()))
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/util/Utils.java b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/util/Utils.java
index 1e7249e..01a0312 100644
--- a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/util/Utils.java
+++ b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/util/Utils.java
@@ -17,6 +17,8 @@
package org.apache.ignite.internal.performancestatistics.util;
+import java.io.PrintStream;
+import com.fasterxml.jackson.core.io.CharTypes;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -26,6 +28,9 @@
/** Json mapper. */
public static final ObjectMapper MAPPER = new ObjectMapper();
+ /** */
+ private final static char[] HC = "0123456789ABCDEF".toCharArray();
+
/** Creates empty object for given value if absent. */
public static ObjectNode createObjectIfAbsent(String val, ObjectNode json) {
ObjectNode node = (ObjectNode)json.get(val);
@@ -51,4 +56,44 @@
return node;
}
+
+ /**
+ * Prints JSON-escaped string to the stream.
+ *
+ * @param ps Print stream to write to.
+ * @param str String to print.
+ * @see CharTypes#appendQuoted(StringBuilder, String)
+ */
+ public static void printEscaped(PrintStream ps, String str) {
+ int[] escCodes = CharTypes.get7BitOutputEscapes();
+
+ int escLen = escCodes.length;
+
+ for (int i = 0, len = str.length(); i < len; ++i) {
+ char c = str.charAt(i);
+
+ if (c >= escLen || escCodes[c] == 0) {
+ ps.print(c);
+
+ continue;
+ }
+
+ ps.print('\\');
+
+ int escCode = escCodes[c];
+
+ if (escCode < 0) {
+ ps.print('u');
+ ps.print('0');
+ ps.print('0');
+
+ int val = c;
+
+ ps.print(HC[val >> 4]);
+ ps.print(HC[val & 0xF]);
+ }
+ else
+ ps.print((char)escCode);
+ }
+ }
}
diff --git a/modules/performance-statistics-ext/src/test/java/org/apache/ignite/internal/performancestatistics/IgnitePerformanceStatisticsReportTestSuite.java b/modules/performance-statistics-ext/src/test/java/org/apache/ignite/internal/performancestatistics/IgnitePerformanceStatisticsReportTestSuite.java
index 35265e7..c21ddb2 100644
--- a/modules/performance-statistics-ext/src/test/java/org/apache/ignite/internal/performancestatistics/IgnitePerformanceStatisticsReportTestSuite.java
+++ b/modules/performance-statistics-ext/src/test/java/org/apache/ignite/internal/performancestatistics/IgnitePerformanceStatisticsReportTestSuite.java
@@ -25,7 +25,8 @@
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
- PerformanceStatisticsReportSelfTest.class
+ PerformanceStatisticsReportSelfTest.class,
+ PerformanceStatisticsPrinterTest.class
})
public class IgnitePerformanceStatisticsReportTestSuite {
}
diff --git a/modules/performance-statistics-ext/src/test/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsPrinterTest.java b/modules/performance-statistics-ext/src/test/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsPrinterTest.java
new file mode 100644
index 0000000..4cd267f
--- /dev/null
+++ b/modules/performance-statistics-ext/src/test/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsPrinterTest.java
@@ -0,0 +1,328 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.internal.performancestatistics;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
+import org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter;
+import org.apache.ignite.internal.processors.performancestatistics.OperationType;
+import org.apache.ignite.internal.util.GridIntList;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.logger.java.JavaLogger;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.util.stream.Collectors.joining;
+import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.PERF_STAT_DIR;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_PUT;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_START;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.JOB;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY_READS;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TASK;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TX_COMMIT;
+import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TX_ROLLBACK;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests the performance statistics printer.
+ */
+public class PerformanceStatisticsPrinterTest {
+ /** Test node ID. */
+ private final static UUID NODE_ID = UUID.randomUUID();
+
+ /** */
+ @Before
+ public void beforeTest() throws Exception {
+ U.delete(new File(U.defaultWorkDirectory()));
+ }
+
+ /** */
+ @After
+ public void afterTest() throws Exception {
+ U.delete(new File(U.defaultWorkDirectory()));
+ }
+
+ /** @throws Exception If failed. */
+ @Test
+ public void testOperationsFilter() throws Exception {
+ createStatistics(writer -> {
+ writer.cacheStart(0, "cache");
+ writer.cacheOperation(CACHE_GET, 0, 0, 0);
+ writer.transaction(GridIntList.asList(0), 0, 0, true);
+ writer.transaction(GridIntList.asList(0), 0, 0, false);
+ writer.query(GridCacheQueryType.SQL_FIELDS, "query", 0, 0, 0, true);
+ writer.queryReads(GridCacheQueryType.SQL_FIELDS, NODE_ID, 0, 0, 0);
+ writer.task(new IgniteUuid(NODE_ID, 0), "task", 0, 0, 0);
+ writer.job(new IgniteUuid(NODE_ID, 0), 0, 0, 0, true);
+ });
+
+ List<OperationType> expOps = F.asList(CACHE_START, CACHE_GET, TX_COMMIT, TX_ROLLBACK, QUERY, QUERY_READS,
+ TASK, JOB);
+
+ checkOperationFilter(null, expOps);
+ checkOperationFilter(F.asList(CACHE_START), F.asList(CACHE_START));
+ checkOperationFilter(F.asList(TASK, JOB), F.asList(TASK, JOB));
+ checkOperationFilter(F.asList(CACHE_PUT), Collections.emptyList());
+ }
+
+ /** */
+ private void checkOperationFilter(List<OperationType> opsArg, List<OperationType> expOps) throws Exception {
+ List<String> args = new LinkedList<>();
+
+ if (opsArg != null) {
+ args.add("--ops");
+
+ args.add(opsArg.stream().map(Enum::toString).collect(joining(",")));
+ }
+
+ List<OperationType> ops = new LinkedList<>(expOps);
+
+ readStatistics(args, json -> {
+ OperationType op = OperationType.valueOf(json.get("op").asText());
+
+ assertTrue("Unexpected operation: " + op, ops.remove(op));
+
+ UUID nodeId = UUID.fromString(json.get("nodeId").asText());
+
+ assertEquals(NODE_ID, nodeId);
+ });
+
+ assertTrue("Expected operations:" + ops, ops.isEmpty());
+ }
+
+ /** @throws Exception If failed. */
+ @Test
+ public void testStartTimeFilter() throws Exception {
+ long startTime1 = 10;
+ long startTime2 = 20;
+
+ createStatistics(writer -> {
+ for (long startTime : new long[] {startTime1, startTime2}) {
+ writer.cacheOperation(CACHE_GET, 0, startTime, 0);
+ writer.transaction(GridIntList.asList(0), startTime, 0, true);
+ writer.transaction(GridIntList.asList(0), startTime, 0, false);
+ writer.query(GridCacheQueryType.SQL_FIELDS, "query", 0, startTime, 0, true);
+ writer.task(new IgniteUuid(NODE_ID, 0), "", startTime, 0, 0);
+ writer.job(new IgniteUuid(NODE_ID, 0), 0, startTime, 0, true);
+ }
+ });
+
+ checkStartTimeFilter(null, null, F.asList(startTime1, startTime2));
+ checkStartTimeFilter(null, startTime1, F.asList(startTime1));
+ checkStartTimeFilter(startTime2, null, F.asList(startTime2));
+ checkStartTimeFilter(startTime1, startTime2, F.asList(startTime1, startTime2));
+ checkStartTimeFilter(startTime2 + 1, null, Collections.emptyList());
+ checkStartTimeFilter(null, startTime1 - 1, Collections.emptyList());
+ }
+
+ /** */
+ private void checkStartTimeFilter(Long fromArg, Long toArg, List<Long> expTimes) throws Exception {
+ List<OperationType> opsWithStartTime = F.asList(CACHE_GET, TX_COMMIT, TX_ROLLBACK, QUERY, TASK, JOB);
+
+ List<String> args = new LinkedList<>();
+
+ if (fromArg != null) {
+ args.add("--from");
+
+ args.add(fromArg.toString());
+ }
+
+ if (toArg != null) {
+ args.add("--to");
+
+ args.add(toArg.toString());
+ }
+
+ Map<Long, List<OperationType>> opsByTime = new HashMap<>();
+
+ for (Long time : expTimes)
+ opsByTime.put(time, new LinkedList<>(opsWithStartTime));
+
+ readStatistics(args, json -> {
+ OperationType op = OperationType.valueOf(json.get("op").asText());
+
+ if (opsWithStartTime.contains(op)) {
+ long startTime = json.get("startTime").asLong();
+
+ assertTrue("Unexpected startTime: " + startTime, opsByTime.containsKey(startTime));
+ assertTrue("Unexpected operation: " + op, opsByTime.get(startTime).remove(op));
+ }
+ });
+
+ assertTrue("Expected operations: " + opsByTime, opsByTime.values().stream().allMatch(List::isEmpty));
+ }
+
+ /** @throws Exception If failed. */
+ @Test
+ public void testCachesFilter() throws Exception {
+ String cache1 = "cache1";
+ String cache2 = "cache2";
+
+ createStatistics(writer -> {
+ for (String cache : new String[] {cache1, cache2}) {
+ writer.cacheStart(CU.cacheId(cache), cache);
+ writer.cacheOperation(CACHE_GET, CU.cacheId(cache), 0, 0);
+ writer.transaction(GridIntList.asList(CU.cacheId(cache)), 0, 0, true);
+ writer.transaction(GridIntList.asList(CU.cacheId(cache)), 0, 0, false);
+ }
+ });
+
+ checkCachesFilter(null, new String[] {cache1, cache2});
+ checkCachesFilter(new String[] {cache1}, new String[] {cache1});
+ checkCachesFilter(new String[] {cache2}, new String[] {cache2});
+ checkCachesFilter(new String[] {cache1, cache2}, new String[] {cache1, cache2});
+ checkCachesFilter(new String[] {"unknown_cache"}, new String[0]);
+ }
+
+ /** */
+ private void checkCachesFilter(String[] cachesArg, String[] expCaches) throws Exception {
+ List<OperationType> cacheIdOps = F.asList(CACHE_START, CACHE_GET, TX_COMMIT, TX_ROLLBACK);
+
+ List<String> args = new LinkedList<>();
+
+ if (cachesArg != null) {
+ args.add("--caches");
+
+ args.add(String.join(",", cachesArg));
+ }
+
+ Map<Integer, List<OperationType>> opsById = new HashMap<>();
+
+ for (String cache : expCaches)
+ opsById.put(CU.cacheId(cache), new LinkedList<>(cacheIdOps));
+
+ readStatistics(args, json -> {
+ OperationType op = OperationType.valueOf(json.get("op").asText());
+
+ Integer id = null;
+
+ if (OperationType.cacheOperation(op) || op == CACHE_START)
+ id = json.get("cacheId").asInt();
+ else if (OperationType.transactionOperation(op)) {
+ assertTrue(json.get("cacheIds").isArray());
+ assertEquals(1, json.get("cacheIds").size());
+
+ id = json.get("cacheIds").get(0).asInt();
+ }
+ else
+ fail("Unexpected operation: " + op);
+
+ assertTrue("Unexpected cache id: " + id, opsById.containsKey(id));
+ assertTrue("Unexpected operation: " + op, opsById.get(id).remove(op));
+ });
+
+ assertTrue("Expected operations: " + opsById, opsById.values().stream().allMatch(List::isEmpty));
+ }
+
+ /** Writes statistics through passed writer. */
+ private void createStatistics(Consumer<FilePerformanceStatisticsWriter> c) throws Exception {
+ FilePerformanceStatisticsWriter writer = new FilePerformanceStatisticsWriter(new TestKernalContext(NODE_ID));
+
+ writer.start();
+
+ waitForCondition(() -> U.field((Object)U.field(writer, "fileWriter"), "runner") != null, 30_000);
+
+ c.accept(writer);
+
+ writer.stop();
+ }
+
+ /**
+ * @param args Additional program arguments.
+ * @param c Consumer to handle operations.
+ * @throws Exception If failed.
+ */
+ private void readStatistics(List<String> args, Consumer<JsonNode> c) throws Exception {
+ File perfStatDir = new File(U.defaultWorkDirectory(), PERF_STAT_DIR);
+
+ assertTrue(perfStatDir.exists());
+
+ File out = new File(U.defaultWorkDirectory(), "report.txt");
+
+ U.delete(out);
+
+ List<String> pArgs = new LinkedList<>();
+
+ pArgs.add(perfStatDir.getAbsolutePath());
+ pArgs.add("--out");
+ pArgs.add(out.getAbsolutePath());
+
+ pArgs.addAll(args);
+
+ PerformanceStatisticsPrinter.main(pArgs.toArray(new String[0]));
+
+ assertTrue(out.exists());
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(out))) {
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ JsonNode json = mapper.readTree(line);
+
+ assertTrue(json.isObject());
+
+ UUID nodeId = UUID.fromString(json.get("nodeId").asText());
+
+ assertEquals(NODE_ID, nodeId);
+
+ c.accept(json);
+ }
+ }
+ }
+
+ /** Test kernal context. */
+ private static class TestKernalContext extends GridTestKernalContext {
+ /** Node ID. */
+ private final UUID nodeId;
+
+ /** @param nodeId Node ID. */
+ public TestKernalContext(UUID nodeId) {
+ super(new JavaLogger());
+
+ this.nodeId = nodeId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public UUID localNodeId() {
+ return nodeId;
+ }
+ }
+}