From 191a6f61a36a62b3a1ca9f40038cd21bb0e3b71f Mon Sep 17 00:00:00 2001 From: Frankie Wu Date: Sun, 4 Mar 2012 11:30:45 +0800 Subject: [PATCH] transaction report draft --- .../cat/consumer/failure/FailureAnalyzer.java | 181 ++++++++ .../MeanSquareDeviationComputer.java | 45 ++ .../transaction/TransactionAnalyzer.java | 267 ++++++++++++ .../com/dianping/cat/consumer/MyTest.java | 39 ++ .../cat/consumer/transaction/FormatTest.java | 57 +++ .../transaction/TransactionAnalyzerTest.java | 75 ++++ .../transaction/TransactionAnalyzerTest.json | 105 +++++ .../transaction/model/transaction-report.xml | 46 ++ .../report/graph/AbstractGraphPayload.java | 88 ++++ .../cat/report/graph/DefaultGraphBuilder.java | 398 ++++++++++++++++++ .../report/graph/DefaultValueTranslater.java | 45 ++ .../cat/report/graph/GraphBuilder.java | 5 + .../cat/report/graph/GraphPayload.java | 33 ++ .../cat/report/graph/ValueTranslater.java | 7 + .../cat/report/page/AbstractReportModel.java | 48 +++ .../cat/report/page/model/Action.java | 26 ++ .../cat/report/page/model/Context.java | 7 + .../cat/report/page/model/Handler.java | 67 +++ .../cat/report/page/model/JspFile.java | 17 + .../cat/report/page/model/JspViewer.java | 18 + .../dianping/cat/report/page/model/Model.java | 45 ++ .../cat/report/page/model/Payload.java | 81 ++++ .../failure/CompositeFailureModelService.java | 94 +++++ .../failure/LocalFailureModelService.java | 57 +++ .../failure/RemoteFailureModelService.java | 81 ++++ .../report/page/model/spi/ModelPeriod.java | 31 ++ .../report/page/model/spi/ModelRequest.java | 56 +++ .../report/page/model/spi/ModelResponse.java | 23 + .../report/page/model/spi/ModelService.java | 7 + .../CompositeTransactionModelService.java | 97 +++++ .../LocalTransactionModelService.java | 57 +++ .../RemoteTransactionModelService.java | 81 ++++ .../transaction/TransactionReportMerger.java | 121 ++++++ cat-home/src/main/webapp/css/transaction.css | 50 +++ cat-home/src/main/webapp/jsp/report/model.jsp | 5 + .../webapp/jsp/report/transaction_graph.jsp | 46 ++ .../webapp/jsp/report/transaction_graphs.jsp | 17 + .../cat/report/graph/ValueTranslaterTest.java | 20 + .../TransactionModelServiceTest.java | 48 +++ 39 files changed, 2591 insertions(+) create mode 100644 cat-consumer/src/main/java/com/dianping/cat/consumer/failure/FailureAnalyzer.java create mode 100644 cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/MeanSquareDeviationComputer.java create mode 100644 cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/TransactionAnalyzer.java create mode 100644 cat-consumer/src/test/java/com/dianping/cat/consumer/MyTest.java create mode 100644 cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/FormatTest.java create mode 100644 cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.java create mode 100644 cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.json create mode 100644 cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/model/transaction-report.xml create mode 100644 cat-home/src/main/java/com/dianping/cat/report/graph/AbstractGraphPayload.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/graph/DefaultGraphBuilder.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/graph/DefaultValueTranslater.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/graph/GraphBuilder.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/graph/GraphPayload.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/graph/ValueTranslater.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/AbstractReportModel.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/Action.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/Context.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/Handler.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/JspFile.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/JspViewer.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/Model.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/Payload.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/failure/CompositeFailureModelService.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/failure/LocalFailureModelService.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/failure/RemoteFailureModelService.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelPeriod.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelRequest.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelResponse.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelService.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/CompositeTransactionModelService.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/LocalTransactionModelService.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/RemoteTransactionModelService.java create mode 100644 cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/TransactionReportMerger.java create mode 100644 cat-home/src/main/webapp/css/transaction.css create mode 100644 cat-home/src/main/webapp/jsp/report/model.jsp create mode 100644 cat-home/src/main/webapp/jsp/report/transaction_graph.jsp create mode 100644 cat-home/src/main/webapp/jsp/report/transaction_graphs.jsp create mode 100644 cat-home/src/test/java/com/dianping/cat/report/graph/ValueTranslaterTest.java create mode 100644 cat-home/src/test/java/com/dianping/cat/report/page/model/transaction/TransactionModelServiceTest.java diff --git a/cat-consumer/src/main/java/com/dianping/cat/consumer/failure/FailureAnalyzer.java b/cat-consumer/src/main/java/com/dianping/cat/consumer/failure/FailureAnalyzer.java new file mode 100644 index 000000000..ce1aa4ec4 --- /dev/null +++ b/cat-consumer/src/main/java/com/dianping/cat/consumer/failure/FailureAnalyzer.java @@ -0,0 +1,181 @@ +package com.dianping.cat.consumer.failure; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.logging.LogEnabled; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable; +import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException; + +import com.dianping.cat.configuration.model.entity.Config; +import com.dianping.cat.configuration.model.entity.Property; +import com.dianping.cat.consumer.failure.model.entity.FailureReport; +import com.dianping.cat.consumer.failure.model.transform.DefaultJsonBuilder; +import com.dianping.cat.message.spi.AbstractMessageAnalyzer; +import com.dianping.cat.message.spi.MessageManager; +import com.dianping.cat.message.spi.MessageStorage; +import com.dianping.cat.message.spi.MessageTree; +import com.site.helper.Files; +import com.site.lookup.annotation.Inject; + +/** + * @author sean.wang + * @since Jan 5, 2012 + */ +public class FailureAnalyzer extends AbstractMessageAnalyzer implements Initializable, LogEnabled { + + private static final SimpleDateFormat FILE_SDF = new SimpleDateFormat("yyyyMMddHHmm"); + + private static final long MINUTE = 60 * 1000; + + @Inject + private MessageManager m_messageManager; + + @Inject + private MessageStorage m_messageStorage; + + private Map m_reports = new HashMap(); + + private long m_extraTime; + + private String m_reportPath; + + private Logger m_logger; + + private long m_startTime; + + private long m_duration; + + @Override + public void enableLogging(Logger logger) { + m_logger = logger; + } + + @Override + protected List generate() { + List reports = new ArrayList(m_reports.size()); + + for (String domain : m_reports.keySet()) { + FailureReport report = generate(domain); + + reports.add(report); + } + + return reports; + } + + FailureReport generate(String domain) { + if (domain == null) { + List domains = getDomains(); + + domain = domains.size() > 0 ? domains.get(0) : null; + } + + FailureReport report = m_reports.get(domain); + + return report; + } + + public List getDomains() { + List domains = new ArrayList(m_reports.keySet()); + + Collections.sort(domains, new Comparator() { + @Override + public int compare(String d1, String d2) { + if (d1.equals("Cat")) { + return 1; + } + + return d1.compareTo(d2); + } + }); + + return domains; + } + + public FailureReport getReport(String domain) { + return m_reports.get(domain); + } + + public Map getReports() { + return m_reports; + } + + private String getFailureFileName(FailureReport report) { + StringBuffer result = new StringBuffer(); + String start = FILE_SDF.format(report.getStartTime()); + String end = FILE_SDF.format(report.getEndTime()); + + result.append(report.getDomain()).append("-").append(start).append("-").append(end); + return result.toString(); + } + + @Override + public void initialize() throws InitializationException { + Config config = m_messageManager.getClientConfig(); + + if (config != null) { + Property property = config.findProperty("failure-base-dir"); + + if (property != null) { + m_reportPath = property.getValue(); + } + } + } + + @Override + protected boolean isTimeout() { + long currentTime = System.currentTimeMillis(); + long endTime = m_startTime + m_duration + m_extraTime; + + return currentTime > endTime; + } + + @Override + protected void process(MessageTree tree) { + + } + + public void setAnalyzerInfo(long startTime, long duration, String domain, long extraTime) { + m_extraTime = extraTime; + m_startTime = startTime; + m_duration = duration; + } + + public void setMessageStorage(MessageStorage messageStorage) { + m_messageStorage = messageStorage; + } + + public void setReportPath(String reportPath) { + m_reportPath = reportPath; + } + + @Override + protected void store(List reports) { + if (reports == null || reports.size() == 0) { + return; + } + + for (FailureReport report : reports) { + String failureFileName = getFailureFileName(report); + String htmlPath = new StringBuilder().append(m_reportPath).append(failureFileName).append(".html").toString(); + File file = new File(htmlPath); + + file.getParentFile().mkdirs(); + + try { + Files.forIO().writeTo(file, new DefaultJsonBuilder().buildJson(report)); + } catch (IOException e) { + m_logger.error(String.format("Error when writing to file(%s)!", file), e); + } + } + } +} diff --git a/cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/MeanSquareDeviationComputer.java b/cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/MeanSquareDeviationComputer.java new file mode 100644 index 000000000..6bf90f04b --- /dev/null +++ b/cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/MeanSquareDeviationComputer.java @@ -0,0 +1,45 @@ +package com.dianping.cat.consumer.transaction; + +import com.dianping.cat.consumer.transaction.model.entity.TransactionName; +import com.dianping.cat.consumer.transaction.model.entity.TransactionType; +import com.dianping.cat.consumer.transaction.model.transform.BaseVisitor; + +public class MeanSquareDeviationComputer extends BaseVisitor { + @Override + public void visitName(TransactionName name) { + long count = name.getTotalCount(); + + if (count > 0) { + long failCount = name.getFailCount(); + double avg = name.getSum() / count; + double std = std(count, avg, name.getSum2()); + double failPercent = 100.0 * failCount / count; + + name.setFailPercent(failPercent); + name.setAvg(avg); + name.setStd(std); + } + } + + @Override + public void visitType(TransactionType type) { + super.visitType(type); + + long count = type.getTotalCount(); + + if (count > 0) { + long failCount = type.getFailCount(); + double avg = type.getSum() / count; + double std = std(count, avg, type.getSum2()); + double failPercent = 100.0 * failCount / count; + + type.setFailPercent(failPercent); + type.setAvg(avg); + type.setStd(std); + } + } + + double std(long count, double avg, double sum2) { + return Math.sqrt(sum2 / count - avg * avg); + } +} \ No newline at end of file diff --git a/cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/TransactionAnalyzer.java b/cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/TransactionAnalyzer.java new file mode 100644 index 000000000..7d841c430 --- /dev/null +++ b/cat-consumer/src/main/java/com/dianping/cat/consumer/transaction/TransactionAnalyzer.java @@ -0,0 +1,267 @@ +package com.dianping.cat.consumer.transaction; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.logging.LogEnabled; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable; +import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException; + +import com.dianping.cat.configuration.model.entity.Config; +import com.dianping.cat.configuration.model.entity.Property; +import com.dianping.cat.consumer.transaction.model.entity.TransactionName; +import com.dianping.cat.consumer.transaction.model.entity.TransactionReport; +import com.dianping.cat.consumer.transaction.model.entity.TransactionType; +import com.dianping.cat.consumer.transaction.model.transform.DefaultJsonBuilder; +import com.dianping.cat.message.Message; +import com.dianping.cat.message.Transaction; +import com.dianping.cat.message.spi.AbstractMessageAnalyzer; +import com.dianping.cat.message.spi.MessageManager; +import com.dianping.cat.message.spi.MessageStorage; +import com.dianping.cat.message.spi.MessageTree; +import com.site.helper.Files; +import com.site.lookup.annotation.Inject; + +/** + * @author sean.wang + * @since Jan 5, 2012 + */ +public class TransactionAnalyzer extends AbstractMessageAnalyzer implements Initializable, + LogEnabled { + + private static final SimpleDateFormat FILE_SDF = new SimpleDateFormat("yyyyMMddHHmm"); + + private static final long MINUTE = 60 * 1000; + + @Inject + private MessageManager m_messageManager; + + @Inject + private MessageStorage m_messageStorage; + + private Map m_reports = new HashMap(); + + private long m_extraTime; + + private String m_reportPath; + + private Logger m_logger; + + private long m_startTime; + + private long m_duration; + + @Override + public void enableLogging(Logger logger) { + m_logger = logger; + } + + @Override + protected List generate() { + List reports = new ArrayList(m_reports.size()); + MeanSquareDeviationComputer computer = new MeanSquareDeviationComputer(); + + for (String domain : m_reports.keySet()) { + TransactionReport report = generate(domain); + + report.accept(computer); + reports.add(report); + } + + return reports; + } + + TransactionReport generate(String domain) { + if (domain == null) { + List domains = getDomains(); + + domain = domains.size() > 0 ? domains.get(0) : null; + } + + TransactionReport report = m_reports.get(domain); + + return report; + } + + public List getDomains() { + List domains = new ArrayList(m_reports.keySet()); + + Collections.sort(domains, new Comparator() { + @Override + public int compare(String d1, String d2) { + if (d1.equals("Cat")) { + return 1; + } + + return d1.compareTo(d2); + } + }); + + return domains; + } + + public TransactionReport getReport(String domain) { + return m_reports.get(domain); + } + + public Map getReports() { + return m_reports; + } + + private String getTransactionFileName(TransactionReport report) { + StringBuffer result = new StringBuffer(); + String start = FILE_SDF.format(report.getStartTime()); + String end = FILE_SDF.format(report.getEndTime()); + + result.append(report.getDomain()).append("-").append(start).append("-").append(end); + return result.toString(); + } + + @Override + public void initialize() throws InitializationException { + Config config = m_messageManager.getClientConfig(); + + if (config != null) { + Property property = config.findProperty("transaction-base-dir"); + + if (property != null) { + m_reportPath = property.getValue(); + } + } + } + + @Override + protected boolean isTimeout() { + long currentTime = System.currentTimeMillis(); + long endTime = m_startTime + m_duration + m_extraTime; + + return currentTime > endTime; + } + + @Override + protected void process(MessageTree tree) { + String domain = tree.getDomain(); + TransactionReport report = m_reports.get(domain); + + if (report == null) { + report = new TransactionReport(domain); + report.setStartTime(new Date(m_startTime)); + report.setEndTime(new Date(m_startTime + MINUTE * 60 - 1)); + + m_reports.put(domain, report); + } + + Message message = tree.getMessage(); + + if (message instanceof Transaction) { + int count = processTransaction(report, tree, (Transaction) message); + + // the message is required by some transactions + if (count > 0) { + m_messageStorage.store(tree); + } + } + } + + int processTransaction(TransactionReport report, MessageTree tree, Transaction t) { + TransactionType type = report.findOrCreateType(t.getType()); + TransactionName name = type.findOrCreateName(t.getName()); + String url = m_messageStorage.getPath(tree); + int count = 0; + + type.incTotalCount(); + name.incTotalCount(); + + if (t.isSuccess()) { + if (type.getSuccessMessageUrl() == null) { + type.setSuccessMessageUrl(url); + count++; + } + + if (name.getSuccessMessageUrl() == null) { + name.setSuccessMessageUrl(url); + count++; + } + } else { + type.incFailCount(); + name.incFailCount(); + + if (type.getFailMessageUrl() == null) { + type.setFailMessageUrl(url); + count++; + } + + if (name.getFailMessageUrl() == null) { + name.setFailMessageUrl(url); + count++; + } + } + + // update statistics + long duration = t.getDuration(); + + name.setMax(Math.max(name.getMax(), duration)); + name.setMin(Math.min(name.getMin(), duration)); + name.setSum(name.getSum() + duration); + name.setSum2(name.getSum2() + duration * duration); + + type.setMax(Math.max(type.getMax(), duration)); + type.setMin(Math.min(type.getMin(), duration)); + type.setSum(type.getSum() + duration); + type.setSum2(type.getSum2() + duration * duration); + + List children = t.getChildren(); + + for (Message child : children) { + if (child instanceof Transaction) { + count += processTransaction(report, tree, (Transaction) child); + } + } + + return count; + } + + public void setAnalyzerInfo(long startTime, long duration, String domain, long extraTime) { + m_extraTime = extraTime; + m_startTime = startTime; + m_duration = duration; + } + + public void setMessageStorage(MessageStorage messageStorage) { + m_messageStorage = messageStorage; + } + + public void setReportPath(String reportPath) { + m_reportPath = reportPath; + } + + @Override + protected void store(List reports) { + if (reports == null || reports.size() == 0) { + return; + } + + for (TransactionReport report : reports) { + String failureFileName = getTransactionFileName(report); + String htmlPath = new StringBuilder().append(m_reportPath).append(failureFileName).append(".html").toString(); + File file = new File(htmlPath); + + file.getParentFile().mkdirs(); + + try { + Files.forIO().writeTo(file, new DefaultJsonBuilder().buildJson(report)); + } catch (IOException e) { + m_logger.error(String.format("Error when writing to file(%s)!", file), e); + } + } + } +} diff --git a/cat-consumer/src/test/java/com/dianping/cat/consumer/MyTest.java b/cat-consumer/src/test/java/com/dianping/cat/consumer/MyTest.java new file mode 100644 index 000000000..ac0256603 --- /dev/null +++ b/cat-consumer/src/test/java/com/dianping/cat/consumer/MyTest.java @@ -0,0 +1,39 @@ +package com.dianping.cat.consumer; + +import junit.framework.Assert; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MyTest { + public static void main(String[] args) { + System.out.println("second"); + System.out.println("second"); + System.out.println("second"); + System.out.println("second"); + System.out.println("second"); + + // second comment + System.out.println("second"); + System.out.println("second"); + System.out.println("second"); + System.out.println("second"); + System.out.println("second"); + System.out.println("second"); + + } + + @Test + public void firstCase() { + Assert.assertEquals("First", "Fir" + "st"); + } + + @Test + @Ignore("need database connection") + public void secondCase() { + System.out.println("Second"); + } +} diff --git a/cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/FormatTest.java b/cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/FormatTest.java new file mode 100644 index 000000000..bb186ad0f --- /dev/null +++ b/cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/FormatTest.java @@ -0,0 +1,57 @@ +package com.dianping.cat.consumer.transaction; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +import junit.framework.Assert; + +import org.junit.Test; + +public class FormatTest { + private void checkFormat(Number number, String format, String expected) { + String actual = new DecimalFormat(format).format(number); + + Assert.assertEquals(expected, actual); + } + + private void checkFranceFormat(Number number, String format, String expected) { + DecimalFormat df = new DecimalFormat(format); + + df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.FRANCE)); + + String actual = df.format(number); + + Assert.assertEquals(expected, actual); + } + + @Test + public void testFormat() { + checkFormat(12, "0", "12"); + checkFormat(12.34, "0", "12"); + + checkFormat(12, "0.#", "12"); + checkFormat(12.34, "0.#", "12.3"); + checkFormat(12.35, "0.#", "12.4"); + + checkFormat(12.34, "0.##", "12.34"); + checkFormat(12.346, "0.##", "12.35"); + + checkFormat(0.3467, "0.#%", "34.7%"); + } + + @Test + public void testFranceFormat() { + checkFranceFormat(12, "0", "12"); + checkFranceFormat(12.34, "0", "12"); + + checkFranceFormat(12, "0.#", "12"); + checkFranceFormat(12.34, "0.#", "12,3"); + checkFranceFormat(12.35, "0.#", "12,4"); + + checkFranceFormat(12.34, "0.##", "12,34"); + checkFranceFormat(12.346, "0.##", "12,35"); + + checkFranceFormat(0.3467, "0.#%", "34,7%"); + } +} diff --git a/cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.java b/cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.java new file mode 100644 index 000000000..cc216f7c3 --- /dev/null +++ b/cat-consumer/src/test/java/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.java @@ -0,0 +1,75 @@ +package com.dianping.cat.consumer.transaction; + +import junit.framework.Assert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import com.dianping.cat.consumer.transaction.model.transform.DefaultJsonBuilder; +import com.dianping.cat.consumer.transaction.model.entity.TransactionReport; +import com.dianping.cat.message.Message; +import com.dianping.cat.message.internal.DefaultTransaction; +import com.dianping.cat.message.spi.MessageStorage; +import com.dianping.cat.message.spi.MessageTree; +import com.dianping.cat.message.spi.internal.DefaultMessageTree; +import com.site.helper.Files; +import com.site.lookup.ComponentTestCase; + +@RunWith(JUnit4.class) +public class TransactionAnalyzerTest extends ComponentTestCase { + @Test + public void testProcessTransaction() throws Exception { + TransactionAnalyzer analyzer = new TransactionAnalyzer(); + TransactionReport report = new TransactionReport("Test"); + + analyzer.setMessageStorage(lookup(MessageStorage.class, "html")); + + for (int i = 1; i <= 1000; i++) { + MessageTree tree = newMessageTree(i); + DefaultTransaction t = new DefaultTransaction("A", "n" + i % 2, null); + DefaultTransaction t2 = new DefaultTransaction("A-1", "n" + i % 3, null); + + if (i % 2 == 0) { + t2.setStatus("ERROR"); + } else { + t2.setStatus(Message.SUCCESS); + } + + t2.complete(); + t2.setDuration(i); + + t.addChild(t2); + + if (i % 2 == 0) { + t.setStatus("ERROR"); + } else { + t.setStatus(Message.SUCCESS); + } + + t.complete(); + t.setDuration(i * 2); + + tree.setMessage(t); + + analyzer.processTransaction(report, tree, t); + } + + report.accept(new MeanSquareDeviationComputer()); + + String json = new DefaultJsonBuilder().buildJson(report); + String expected = Files.forIO().readFrom(getClass().getResourceAsStream("TransactionAnalyzerTest.json"), "utf-8"); + + Assert.assertEquals(expected.replace("\r", ""), json.replace("\r", "")); + } + + protected MessageTree newMessageTree(int i) { + MessageTree tree = new DefaultMessageTree(); + + tree.setMessageId("" + i); + tree.setDomain("group"); + tree.setHostName("group001"); + tree.setIpAddress("192.168.1.1"); + return tree; + } +} diff --git a/cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.json b/cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.json new file mode 100644 index 000000000..1425c8b2d --- /dev/null +++ b/cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/TransactionAnalyzerTest.json @@ -0,0 +1,105 @@ +{ + "domain": "Test", + "types": { + "A": { + "id": "A", + "successMessageUrl": "20120228/21/group/1.html", + "failMessageUrl": "20120228/21/group/2.html", + "totalCount": 1000, + "failCount": 500, + "failPercent": "50.00", + "min": 2.0, + "max": 2000.0, + "avg": "1001.0", + "sum": "1001000.0", + "sum2": "1335334000.0", + "std": "577.3", + "names": { + "n1": { + "id": "n1", + "totalCount": 500, + "failCount": 0, + "failPercent": "0.00", + "min": 2.0, + "max": 1998.0, + "avg": "1000.0", + "sum": "500000.0", + "sum2": "666666000.0", + "std": "577.3", + "successMessageUrl": "20120228/21/group/1.html" + }, + "n0": { + "id": "n0", + "totalCount": 500, + "failCount": 500, + "failPercent": "100.00", + "min": 4.0, + "max": 2000.0, + "avg": "1002.0", + "sum": "501000.0", + "sum2": "668668000.0", + "std": "577.3", + "failMessageUrl": "20120228/21/group/2.html" + } + } + }, + "A-1": { + "id": "A-1", + "successMessageUrl": "20120228/21/group/1.html", + "failMessageUrl": "20120228/21/group/2.html", + "totalCount": 1000, + "failCount": 500, + "failPercent": "50.00", + "min": 1.0, + "max": 1000.0, + "avg": "500.5", + "sum": "500500.0", + "sum2": "333833500.0", + "std": "288.7", + "names": { + "n1": { + "id": "n1", + "totalCount": 334, + "failCount": 167, + "failPercent": "50.00", + "min": 1.0, + "max": 1000.0, + "avg": "500.5", + "sum": "167167.0", + "sum2": "111611611.0", + "std": "289.3", + "successMessageUrl": "20120228/21/group/1.html", + "failMessageUrl": "20120228/21/group/4.html" + }, + "n2": { + "id": "n2", + "totalCount": 333, + "failCount": 167, + "failPercent": "50.15", + "min": 2.0, + "max": 998.0, + "avg": "500.0", + "sum": "166500.0", + "sum2": "110944278.0", + "std": "288.4", + "successMessageUrl": "20120228/21/group/5.html", + "failMessageUrl": "20120228/21/group/2.html" + }, + "n0": { + "id": "n0", + "totalCount": 333, + "failCount": 166, + "failPercent": "49.85", + "min": 3.0, + "max": 999.0, + "avg": "501.0", + "sum": "166833.0", + "sum2": "111277611.0", + "std": "288.4", + "successMessageUrl": "20120228/21/group/3.html", + "failMessageUrl": "20120228/21/group/6.html" + } + } + } + } +} diff --git a/cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/model/transaction-report.xml b/cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/model/transaction-report.xml new file mode 100644 index 000000000..06bc00810 --- /dev/null +++ b/cat-consumer/src/test/resources/com/dianping/cat/consumer/transaction/model/transaction-report.xml @@ -0,0 +1,46 @@ + + Cat + + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + + + + + + 20120216/23/Cat/b10bdefb-1eca-45e1-a9c3-52367078b5a2.html + + + 20120216/23/Cat/8e7c91a0-7549-4b13-b43b-252b2a6ef4bb.html + + + 20120216/23/Cat/2029c32e-b692-4e43-8eaf-96b8d6c846a2.html + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + 20120216/23/Cat/1168a02c-664b-440c-9ef4-a87bac4d9cb1.html + + diff --git a/cat-home/src/main/java/com/dianping/cat/report/graph/AbstractGraphPayload.java b/cat-home/src/main/java/com/dianping/cat/report/graph/AbstractGraphPayload.java new file mode 100644 index 000000000..b2bca9ebd --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/graph/AbstractGraphPayload.java @@ -0,0 +1,88 @@ +package com.dianping.cat.report.graph; + +public abstract class AbstractGraphPayload implements GraphPayload { + private double[] m_values; + + private String m_title; + + private String m_axisXLabel; + + private String m_axisYLabel; + + public AbstractGraphPayload(String title, String axisXLabel, String axisYLabel) { + m_title = title; + m_axisXLabel = axisXLabel; + m_axisYLabel = axisYLabel; + m_values = getValues(); + } + + @Override + public String getAxisXLabel() { + return m_axisXLabel; + } + + @Override + public String getAxisYLabel() { + return m_axisYLabel; + } + + @Override + public int getColumns() { + return m_values.length; + } + + @Override + public String getDescription() { + return null; + } + + @Override + public int getDisplayHeight() { + return getHeight(); + } + + @Override + public int getDisplayWidth() { + return getWidth(); + } + + @Override + public int getHeight() { + return 280; + } + + @Override + public int getMarginBottom() { + return 50; + } + + @Override + public int getMarginLeft() { + return 90; + } + + @Override + public int getMarginRight() { + return 10; + } + + @Override + public int getMarginTop() { + return 50; + } + + @Override + public int getRows() { + return 5; + } + + @Override + public String getTitle() { + return m_title; + } + + @Override + public int getWidth() { + return 580; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/graph/DefaultGraphBuilder.java b/cat-home/src/main/java/com/dianping/cat/report/graph/DefaultGraphBuilder.java new file mode 100644 index 000000000..be36c0f3f --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/graph/DefaultGraphBuilder.java @@ -0,0 +1,398 @@ +package com.dianping.cat.report.graph; + +import java.text.DecimalFormat; + +import com.site.lookup.annotation.Inject; + +public class DefaultGraphBuilder implements GraphBuilder { + @Inject + private ValueTranslater m_translater; + + @Override + public String build(GraphPayload payload) { + double[] values = payload.getValues(); + int maxValue = m_translater.getMaxValue(values); + XmlBuilder b = new XmlBuilder(); + + buildHeader(payload, b); + buildCoordinate(payload, b); + buildYLabels(payload, b, maxValue); + buildXLabels(payload, b); + buildBars(payload, b, maxValue, values); + buildFooter(payload, b); + + return b.getResult().toString(); + } + + protected void buildBars(GraphPayload payload, XmlBuilder b, int maxValue, double[] values) { + DecimalFormat format = new DecimalFormat("0.#"); + int width = payload.getWidth(); + int height = payload.getHeight(); + int top = payload.getMarginTop(); + int left = payload.getMarginLeft(); + int bottom = payload.getMarginBottom(); + int right = payload.getMarginRight(); + int h = height - top - bottom; + int w = width - left - right; + int cols = payload.getColumns(); + int xstep = w / cols; + int[] pixels = m_translater.translate(h, maxValue, values); + + b.tag1("g", "id", "bar", "fill", "red"); + + for (int i = 0; i < cols && i < pixels.length; i++) { + int pixel = pixels[i]; + int x = left + xstep * i; + int y = top + h - pixel; + + b.tag("rect", "id", "b" + i, "x", x, "y", y, "width", xstep - 1, "height", pixel); + } + + b.tag2("g"); + + b.tag1("g", "id", "label"); + + for (int i = 0; i < cols && i < pixels.length; i++) { + int pixel = pixels[i]; + double value = values[i]; + int x = left + xstep * i; + int y = top - 6 + h - pixel; + String tip = format.format(value); + + // adjust + if (x + tip.length() * 7 > width - right) { + x = width - right - tip.length() * 7; + } + + b.tag1("text", "x", x, "y", y, "display", "none"); + + b.indent().add(tip).newLine(); + b.tag("set", "attributeName", "display", "from", "none", "to", "block", "begin", "b" + i + ".mouseover", + "end", "b" + i + ".mouseout"); + b.tag2("text"); + } + + b.tag2("g"); + } + + protected void buildCoordinate(GraphPayload payload, XmlBuilder b) { + int width = payload.getWidth(); + int height = payload.getHeight(); + int top = payload.getMarginTop(); + int left = payload.getMarginLeft(); + int bottom = payload.getMarginBottom(); + int right = payload.getMarginRight(); + int h = height - top - bottom; + int w = width - left - right; + int rows = payload.getRows(); + int cols = payload.getColumns(); + int ystep = h / rows; + int xstep = w / cols; + PathBuilder p = new PathBuilder(); + + b.tag1("g", "id", "coordinate", "stroke", "#003f7f", "fill", "white"); + b.tag("path", "id", "xy", "d", p.moveTo(left, top + h).h(w).m(-w, 0).v(-h).build()); + b.tag("path", "id", "xy-2", "d", p.moveTo(left, top).m(w, 0).v(h).build(), "stroke-dasharray", "1,5"); + b.tag("path", "id", "lines", "d", p.moveTo(left, top).mark().h(w).m(-w, ystep).repeat(rows - 1).build(), + "stroke-dasharray", "1,5"); + + if (rows >= 8) { + p.moveTo(left, top).mark().h(-9).m(9, ystep).h(-5).m(5, ystep).repeat(rows / 2 - 1); + + if (rows % 2 == 0) { + p.h(-9).m(9, ystep); + } + + b.tag("path", "id", "ys", "d", p.build()); + } else { + p.moveTo(left, top).mark().h(-7).m(7, ystep).repeat(rows); + b.tag("path", "id", "ys", "d", p.build()); + } + + if (cols >= 16) { + p.moveTo(left, top + h).mark().v(9).m(xstep, -9).v(5).m(xstep, -5).repeat(cols / 2); + + if (cols % 2 == 0) { + p.v(9).m(xstep, -9); + } + + b.tag("path", "id", "xs", "d", p.build()); + } else { + p.moveTo(left, top + h).mark().v(7).m(xstep, -7).repeat(cols); + b.tag("path", "id", "xs", "d", p.build()); + } + + b.tag2("g"); + } + + protected void buildFooter(GraphPayload payload, XmlBuilder b) { + b.tag2("svg"); + } + + protected void buildHeader(GraphPayload payload, XmlBuilder b) { + int height = payload.getHeight(); + int width = payload.getWidth(); + int top = payload.getMarginTop(); + int left = payload.getMarginLeft(); + int bottom = payload.getMarginBottom(); + int right = payload.getMarginRight(); + + b.add("\r\n"); + b.tag1("svg", "width", payload.getDisplayWidth(), "height", payload.getDisplayHeight(), "viewBox", "0,0," + width + + "," + height, "xmlns", "http://www.w3.org/2000/svg"); + + String title = payload.getTitle(); + + if (title != null) { + b.element("title", title); + } + + if (payload.getDescription() != null) { + b.element("description", payload.getDescription()); + } + + b.tag1("g"); + + String axisXLabel = payload.getAxisXLabel(); + + if (axisXLabel != null) { + int x = (width - left - right - axisXLabel.length() * 9) / 2 + left; + int y = height - 4; + b.tagWithText("text", axisXLabel, "x", x, "y", y, "font-size", "18"); + } + + String axisYLabel = payload.getAxisYLabel(); + + if (axisYLabel != null) { + int x = 16; + int y = (height - top - bottom + axisYLabel.length() * 9) / 2 + top; + String transform = "rotate(-90," + x + "," + y + ")"; + + b.tagWithText("text", axisYLabel, "x", x, "y", y, "font-size", "18", "transform", transform); + } + + if (title != null) { + int x = (width - left - right - title.length() * 12) / 2 + left; + int y = 24; + b.tagWithText("text", title, "x", x, "y", y, "font-size", "24"); + } + + b.tag2("g"); + } + + protected void buildXLabels(GraphPayload payload, XmlBuilder b) { + int height = payload.getHeight(); + int width = payload.getWidth(); + int left = payload.getMarginLeft(); + int bottom = payload.getMarginBottom(); + int right = payload.getMarginRight(); + int cols = payload.getColumns(); + int w = width - left - right; + int xstep = w / cols; + + b.tag1("g", "id", "xt"); + + if (cols >= 16) { + for (int i = 0; i <= cols; i += 2) { + int x = left + xstep * i - 4; + int y = height - bottom + 22; + + if (i >= 10) { + x -= 4; + } + + b.tagWithText("text", i, "x", x, "y", y); + } + } else { + for (int i = 0; i <= cols; i++) { + int x = left + xstep * i - 4; + int y = height - bottom + 20; + + if (i >= 10) { + x -= 4; + } + + b.tagWithText("text", i, "x", x, "y", y); + } + } + + b.tag2("g"); + } + + protected void buildYLabels(GraphPayload payload, XmlBuilder b, int maxValue) { + int height = payload.getHeight(); + int top = payload.getMarginTop(); + int left = payload.getMarginLeft(); + int bottom = payload.getMarginBottom(); + int h = height - top - bottom; + int rows = payload.getRows(); + int ystep = h / rows; + + b.tag1("g", "id", "yt", "direction", "rtl"); + + if (rows >= 8) { + for (int i = 0; i < rows; i += 2) { + int x = left - 12; + int y = top + 4 + ystep * i; + + b.tagWithText("text", maxValue - maxValue / rows * i, "x", x, "y", y); + } + } else { + for (int i = 0; i < rows; i++) { + int x = left - 9; + int y = top + 4 + ystep * i; + + b.tagWithText("text", maxValue - maxValue / rows * i, "x", x, "y", y); + } + } + + b.tag2("g"); + } + + protected static class PathBuilder { + private StringBuilder m_sb = new StringBuilder(64); + + private int m_marker; + + public String build() { + String result = m_sb.toString(); + + m_sb.setLength(0); + return result; + } + + public PathBuilder h(int deltaX) { + m_sb.append(" h").append(deltaX); + return this; + } + + public PathBuilder m(int deltaX, int deltaY) { + m_sb.append(" m").append(deltaX).append(',').append(deltaY); + return this; + } + + public PathBuilder mark() { + m_marker = m_sb.length(); + return this; + } + + public PathBuilder moveTo(int x, int y) { + m_sb.append('M').append(x).append(',').append(y); + return this; + } + + public PathBuilder repeat(int count) { + int pos = m_sb.length(); + + for (int i = 0; i < count; i++) { + m_sb.append(m_sb.subSequence(m_marker, pos)); + } + + return this; + } + + public PathBuilder v(int deltaY) { + m_sb.append(" v").append(deltaY); + return this; + } + } + + protected static class XmlBuilder { + private StringBuilder m_sb = new StringBuilder(8192); + + private boolean m_compact; + + private int m_level; + + public XmlBuilder add(String text) { + m_sb.append(text); + return this; + } + + public XmlBuilder element(String name, String value) { + indent(); + m_sb.append('<').append(name).append('>'); + m_sb.append(value); + m_sb.append(""); + newLine(); + return this; + } + + public StringBuilder getResult() { + return m_sb; + } + + public XmlBuilder indent() { + if (!m_compact) { + for (int i = m_level - 1; i >= 0; i--) { + m_sb.append(" "); + } + } + + return this; + } + + public XmlBuilder newLine() { + m_sb.append("\r\n"); + return this; + } + + public XmlBuilder tag(String name, Object... attributes) { + return tagWithText(name, null, attributes); + } + + public XmlBuilder tag1(String name, Object... attributes) { + indent(); + + m_sb.append('<').append(name); + + int len = attributes.length; + for (int i = 0; i < len; i += 2) { + Object key = attributes[i]; + Object val = attributes[i + 1]; + + if (val != null) { + m_sb.append(' ').append(key).append("=\"").append(val).append('"'); + } + } + + m_sb.append(">"); + newLine(); + m_level++; + return this; + } + + public XmlBuilder tag2(String name) { + m_level--; + indent(); + m_sb.append(""); + newLine(); + return this; + } + + public XmlBuilder tagWithText(String name, Object text, Object... attributes) { + indent(); + + m_sb.append('<').append(name); + + int len = attributes.length; + for (int i = 0; i < len; i += 2) { + Object key = attributes[i]; + Object val = attributes[i + 1]; + + if (val != null) { + m_sb.append(' ').append(key).append("=\"").append(val).append('"'); + } + } + + if (text == null) { + m_sb.append("/>"); + } else { + m_sb.append('>').append(text).append("'); + } + + newLine(); + return this; + } + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/graph/DefaultValueTranslater.java b/cat-home/src/main/java/com/dianping/cat/report/graph/DefaultValueTranslater.java new file mode 100644 index 000000000..670ad19a6 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/graph/DefaultValueTranslater.java @@ -0,0 +1,45 @@ +package com.dianping.cat.report.graph; + +public class DefaultValueTranslater implements ValueTranslater { + @Override + public int getMaxValue(double[] values) { + double min = Integer.MAX_VALUE; + double max = Integer.MIN_VALUE; + int len = values.length; + + for (int i = 0; i < len; i++) { + double value = values[i]; + + if (value < min) { + min = value; + } + + if (value > max) { + max = value; + } + } + + double maxLog = Math.log10(max); + int maxValue = (int) Math.pow(10, Math.ceil(maxLog)); + + while (maxValue > max * 2) { + maxValue = maxValue / 2; + } + + return maxValue; + } + + @Override + public int[] translate(int height, int maxValue, double[] values) { + int len = values.length; + int[] result = new int[len]; + + for (int i = 0; i < len; i++) { + double value = values[i]; + + result[i] = (int) (value * height / maxValue); + } + + return result; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/graph/GraphBuilder.java b/cat-home/src/main/java/com/dianping/cat/report/graph/GraphBuilder.java new file mode 100644 index 000000000..98459eb6f --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/graph/GraphBuilder.java @@ -0,0 +1,5 @@ +package com.dianping.cat.report.graph; + +public interface GraphBuilder { + public String build(GraphPayload payload); +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/graph/GraphPayload.java b/cat-home/src/main/java/com/dianping/cat/report/graph/GraphPayload.java new file mode 100644 index 000000000..549a4869d --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/graph/GraphPayload.java @@ -0,0 +1,33 @@ +package com.dianping.cat.report.graph; + +public interface GraphPayload { + public String getAxisXLabel(); + + public String getAxisYLabel(); + + public int getColumns(); + + public String getDescription(); + + public int getHeight(); + + public int getMarginBottom(); + + public int getMarginLeft(); + + public int getMarginRight(); + + public int getMarginTop(); + + public int getRows(); + + public String getTitle(); + + public double[] getValues(); + + public int getWidth(); + + public int getDisplayHeight(); + + public int getDisplayWidth(); +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/graph/ValueTranslater.java b/cat-home/src/main/java/com/dianping/cat/report/graph/ValueTranslater.java new file mode 100644 index 000000000..2814350ec --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/graph/ValueTranslater.java @@ -0,0 +1,7 @@ +package com.dianping.cat.report.graph; + +public interface ValueTranslater { + public int getMaxValue(double[] values); + + public int[] translate(int height, int maxValue, double[] values); +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/AbstractReportModel.java b/cat-home/src/main/java/com/dianping/cat/report/page/AbstractReportModel.java new file mode 100644 index 000000000..8167106f2 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/AbstractReportModel.java @@ -0,0 +1,48 @@ +package com.dianping.cat.report.page; + +import java.util.Collection; +import java.util.Date; + +import com.dianping.cat.report.ReportPage; +import com.dianping.cat.report.view.UrlNav; +import com.site.web.mvc.Action; +import com.site.web.mvc.ActionContext; +import com.site.web.mvc.ViewModel; + +public abstract class AbstractReportModel> extends + ViewModel { + private Throwable m_exception; + + public AbstractReportModel(M ctx) { + super(ctx); + } + + public String getBaseUri() { + return buildPageUri(getPage().getPath(), null); + } + + // required by report tag + public Date getCurrentTime() { + return new Date(); + } + + // required by report tag + public abstract Collection getDomains(); + + public Throwable getException() { + return m_exception; + } + + public String getLogViewBaseUri() { + return buildPageUri(ReportPage.LOGVIEW.getPath(), null); + } + + // required by report tag + public UrlNav[] getNavs() { + return UrlNav.values(); + } + + public void setException(Throwable exception) { + m_exception = exception; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/Action.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/Action.java new file mode 100644 index 000000000..13b92a174 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/Action.java @@ -0,0 +1,26 @@ +package com.dianping.cat.report.page.model; + +public enum Action implements com.site.web.mvc.Action { + XML("xml"); + + private String m_name; + + private Action(String name) { + m_name = name; + } + + public static Action getByName(String name, Action defaultAction) { + for (Action action : Action.values()) { + if (action.getName().equals(name)) { + return action; + } + } + + return defaultAction; + } + + @Override + public String getName() { + return m_name; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/Context.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/Context.java new file mode 100644 index 000000000..a618b17fe --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/Context.java @@ -0,0 +1,7 @@ +package com.dianping.cat.report.page.model; + +import com.dianping.cat.report.ReportContext; + +public class Context extends ReportContext { + +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/Handler.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/Handler.java new file mode 100644 index 000000000..f45992de8 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/Handler.java @@ -0,0 +1,67 @@ +package com.dianping.cat.report.page.model; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import com.dianping.cat.report.ReportPage; +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.dianping.cat.report.page.model.transaction.LocalTransactionModelService; +import com.site.lookup.ContainerHolder; +import com.site.lookup.annotation.Inject; +import com.site.web.mvc.PageHandler; +import com.site.web.mvc.annotation.InboundActionMeta; +import com.site.web.mvc.annotation.OutboundActionMeta; +import com.site.web.mvc.annotation.PayloadMeta; + +public class Handler extends ContainerHolder implements PageHandler { + @Inject + private JspViewer m_jspViewer; + + @Inject(type = ModelService.class, value = "transaction-local") + private LocalTransactionModelService m_transactionService; + + @Override + @PayloadMeta(Payload.class) + @InboundActionMeta(name = "model") + public void handleInbound(Context ctx) throws ServletException, IOException { + // display only, no action here + } + + @Override + @OutboundActionMeta(name = "model") + public void handleOutbound(Context ctx) throws ServletException, IOException { + Model model = new Model(ctx); + Payload payload = ctx.getPayload(); + + model.setAction(Action.XML); + model.setPage(ReportPage.MODEL); + + try { + String report = payload.getReport(); + String domain = payload.getDomain(); + ModelRequest request = new ModelRequest(domain, payload.getPeriod()); + ModelResponse response = null; + + if ("transaction".equals(report)) { + response = m_transactionService.invoke(request); + } else if ("failure".equals(report)) { + request.setProperty("ip", payload.getIp()); + // response = m_failureService.invoke(request); + } else { + throw new RuntimeException("Unsupported report: " + report + "!"); + } + + Object dataModel = response.getModel(); + + model.setModel(dataModel); + model.setModelInXml(dataModel == null ? "" : String.valueOf(dataModel)); + } catch (Throwable e) { + model.setException(e); + } + + m_jspViewer.view(ctx, model); + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/JspFile.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/JspFile.java new file mode 100644 index 000000000..5d02028d3 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/JspFile.java @@ -0,0 +1,17 @@ +package com.dianping.cat.report.page.model; + +public enum JspFile { + VIEW("/jsp/report/model.jsp"), + + ; + + private String m_path; + + private JspFile(String path) { + m_path = path; + } + + public String getPath() { + return m_path; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/JspViewer.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/JspViewer.java new file mode 100644 index 000000000..6b7eaa1d0 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/JspViewer.java @@ -0,0 +1,18 @@ +package com.dianping.cat.report.page.model; + +import com.dianping.cat.report.ReportPage; +import com.site.web.mvc.view.BaseJspViewer; + +public class JspViewer extends BaseJspViewer { + @Override + protected String getJspFilePath(Context ctx, Model model) { + Action action = model.getAction(); + + switch (action) { + case XML: + return JspFile.VIEW.getPath(); + } + + throw new RuntimeException("Unknown action: " + action); + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/Model.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/Model.java new file mode 100644 index 000000000..c53d59f63 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/Model.java @@ -0,0 +1,45 @@ +package com.dianping.cat.report.page.model; + +import com.dianping.cat.report.ReportPage; +import com.site.web.mvc.ViewModel; + +public class Model extends ViewModel { + private Throwable m_exception; + + private Object m_model; + + private String m_modelInXml; + + public Model(Context ctx) { + super(ctx); + } + + @Override + public Action getDefaultAction() { + return Action.XML; + } + + public Throwable getException() { + return m_exception; + } + + public Object getModel() { + return m_model; + } + + public String getModelInXml() { + return m_modelInXml; + } + + public void setException(Throwable exception) { + m_exception = exception; + } + + public void setModel(Object model) { + m_model = model; + } + + public void setModelInXml(String modelInXml) { + m_modelInXml = modelInXml; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/Payload.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/Payload.java new file mode 100644 index 000000000..fd4aef4de --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/Payload.java @@ -0,0 +1,81 @@ +package com.dianping.cat.report.page.model; + +import com.dianping.cat.report.ReportPage; +import com.dianping.cat.report.page.model.spi.ModelPeriod; +import com.site.web.mvc.ActionContext; +import com.site.web.mvc.ActionPayload; +import com.site.web.mvc.payload.annotation.FieldMeta; +import com.site.web.mvc.payload.annotation.PathMeta; + +public class Payload implements ActionPayload { + private ReportPage m_page; + + @FieldMeta("op") + private Action m_action; + + // /// + @PathMeta("path") + private String[] m_path; + + @FieldMeta("ip") + private String m_ip; + + @Override + public Action getAction() { + return m_action; + } + + public String getDomain() { + if (m_path.length > 1) { + return m_path[1]; + } else { + return null; + } + } + + public String getIp() { + return m_ip; + } + + @Override + public ReportPage getPage() { + return m_page; + } + + public ModelPeriod getPeriod() { + if (m_path.length > 2) { + return ModelPeriod.getByName(m_path[2], ModelPeriod.CURRENT); + } else { + return ModelPeriod.CURRENT; + } + } + + public String getReport() { + if (m_path.length > 0) { + return m_path[0]; + } else { + return null; + } + } + + public void setAction(Action action) { + m_action = action; + } + + public void setIp(String ip) { + m_ip = ip; + } + + @Override + public void setPage(String page) { + m_page = ReportPage.getByName(page, ReportPage.MODEL); + } + + public void setPath(String[] path) { + m_path = path; + } + + @Override + public void validate(ActionContext ctx) { + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/CompositeFailureModelService.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/CompositeFailureModelService.java new file mode 100644 index 000000000..7b43cd390 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/CompositeFailureModelService.java @@ -0,0 +1,94 @@ +package com.dianping.cat.report.page.model.failure; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable; +import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException; + +import com.dianping.cat.consumer.failure.model.entity.FailureReport; +import com.dianping.cat.consumer.failure.model.transform.DefaultMerger; +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.site.lookup.annotation.Inject; + +public class CompositeFailureModelService implements ModelService, Initializable { + @Inject + private List> m_services; + + private ExecutorService m_threadPool; + + @Override + public void initialize() throws InitializationException { + m_threadPool = Executors.newFixedThreadPool(10); + } + + @Override + public ModelResponse invoke(final ModelRequest request) { + int size = m_services.size(); + final List> responses = new ArrayList>(size); + final CountDownLatch latch = new CountDownLatch(size); + + for (final ModelService service : m_services) { + m_threadPool.submit(new Runnable() { + @Override + public void run() { + try { + responses.add(service.invoke(request)); + } catch (Exception e) { + e.printStackTrace(); + } finally { + latch.countDown(); + } + } + }); + } + + try { + latch.await(5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // ignore it + } + + ModelResponse aggregated = new ModelResponse(); + DefaultMerger merger = null; + + for (ModelResponse response : responses) { + if (response != null) { + FailureReport model = response.getModel(); + + if (model != null) { + if (merger == null) { + merger = new DefaultMerger(model); + } else { + model.accept(merger); + } + } + } + } + + aggregated.setModel(merger == null ? null : merger.getFailureReport()); + return aggregated; + } + + @Override + public boolean isEligable(ModelRequest request) { + for (ModelService service : m_services) { + if (service.isEligable(request)) { + return true; + } + } + + return false; + } + + public void setSerivces(ModelService... services) { + m_services = Arrays.asList(services); + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/LocalFailureModelService.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/LocalFailureModelService.java new file mode 100644 index 000000000..8685984e2 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/LocalFailureModelService.java @@ -0,0 +1,57 @@ +package com.dianping.cat.report.page.model.failure; + +import java.util.List; + +import com.dianping.cat.consumer.RealtimeConsumer; +import com.dianping.cat.consumer.failure.FailureAnalyzer; +import com.dianping.cat.consumer.failure.model.entity.FailureReport; +import com.dianping.cat.message.spi.MessageConsumer; +import com.dianping.cat.report.page.model.spi.ModelPeriod; +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.site.lookup.annotation.Inject; + +public class LocalFailureModelService implements ModelService { + @Inject(type = MessageConsumer.class, value = "realtime") + private RealtimeConsumer m_consumer; + + @Override + public ModelResponse invoke(ModelRequest request) { + FailureAnalyzer analyzer = getAnalyzer(request.getPeriod()); + ModelResponse response = new ModelResponse(); + + if (analyzer != null) { + List domains = analyzer.getDomains(); + String d = request.getDomain(); + FailureReport report = analyzer.getReport(d != null ? d : domains.isEmpty() ? null : domains.get(0)); + + if (report != null) { + for (String domain : domains) { + report.addDomain(domain); + } + } + + response.setModel(report); + } + + return response; + } + + private FailureAnalyzer getAnalyzer(ModelPeriod period) { + if (period.isCurrent()) { + return (FailureAnalyzer) m_consumer.getCurrentAnalyzer("failure"); + } else if (period.isLast()) { + return (FailureAnalyzer) m_consumer.getLastAnalyzer("failure"); + } else { + return null; + } + } + + @Override + public boolean isEligable(ModelRequest request) { + ModelPeriod period = request.getPeriod(); + + return period.isCurrent() || period.isLast(); + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/RemoteFailureModelService.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/RemoteFailureModelService.java new file mode 100644 index 000000000..f87eb06f2 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/failure/RemoteFailureModelService.java @@ -0,0 +1,81 @@ +package com.dianping.cat.report.page.model.failure; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.Map.Entry; + +import com.dianping.cat.consumer.failure.model.entity.FailureReport; +import com.dianping.cat.consumer.failure.model.transform.DefaultXmlParser; +import com.dianping.cat.report.page.model.spi.ModelPeriod; +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.site.helper.Files; +import com.site.helper.Joiners; +import com.site.helper.Joiners.IBuilder; +import com.site.lookup.annotation.Inject; + +public class RemoteFailureModelService implements ModelService { + @Inject + private String m_host; + + @Inject + private int m_port = 2281; // default admin port + + @Inject + private String m_serviceUri = "/cat/r/model"; + + URL buildUrl(ModelRequest request) throws MalformedURLException { + String pairs = Joiners.by('&').prefixDelimiter() + .join(request.getProperties().entrySet(), new IBuilder>() { + @Override + public String asString(Entry e) { + return e.getKey() + "=" + e.getValue(); + } + }); + String url = String.format("http://%s:%s%s/%s/%s/%s?op=xml%s", m_host, m_port, m_serviceUri, "failure", + request.getDomain(), request.getPeriod(), pairs); + + return new URL(url); + } + + @Override + public ModelResponse invoke(ModelRequest request) { + ModelResponse response = new ModelResponse(); + + try { + URL url = buildUrl(request); + String xml = Files.forIO().readFrom(url.openStream(), "utf-8"); + + if (xml != null && xml.trim().length() > 0) { + FailureReport report = new DefaultXmlParser().parse(xml); + + response.setModel(report); + } + } catch (Exception e) { + response.setException(e); + } + + return response; + } + + @Override + public boolean isEligable(ModelRequest request) { + ModelPeriod period = request.getPeriod(); + + return period.isCurrent() || period.isLast(); + } + + public void setHost(String host) { + m_host = host; + } + + public void setPort(int port) { + m_port = port; + } + + public void setServiceUri(String serviceUri) { + m_serviceUri = serviceUri; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelPeriod.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelPeriod.java new file mode 100644 index 000000000..9860f6307 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelPeriod.java @@ -0,0 +1,31 @@ +package com.dianping.cat.report.page.model.spi; + +public enum ModelPeriod { + HISTORICAL, LAST, CURRENT, FUTURE; + + public static ModelPeriod getByName(String name, ModelPeriod defaultValue) { + for (ModelPeriod period : values()) { + if (period.name().equals(name)) { + return period; + } + } + + return defaultValue; + } + + public boolean isCurrent() { + return this == CURRENT; + } + + public boolean isLast() { + return this == LAST; + } + + public boolean isHistorical() { + return this == HISTORICAL; + } + + public boolean isFuture() { + return this == FUTURE; + } +} \ No newline at end of file diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelRequest.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelRequest.java new file mode 100644 index 000000000..ff3ccb239 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelRequest.java @@ -0,0 +1,56 @@ +package com.dianping.cat.report.page.model.spi; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ModelRequest { + private String m_domain; + + private ModelPeriod m_period; + + private Map m_properties; + + public ModelRequest(String domain, ModelPeriod period) { + m_domain = domain; + m_period = period; + } + + public String getDomain() { + return m_domain; + } + + public ModelPeriod getPeriod() { + return m_period; + } + + public Map getProperties() { + if (m_properties == null) { + return Collections.emptyMap(); + } else { + return m_properties; + } + } + + public String getProperty(String name) { + if (m_properties == null) { + return null; + } else { + return m_properties.get(name); + } + } + + public void setProperty(String name, String value) { + if (m_properties == null) { + m_properties = new HashMap(); + } + + m_properties.put(name, value); + } + + public static ModelRequest from(String domain, String period) { + ModelRequest request = new ModelRequest(domain, ModelPeriod.getByName(period, ModelPeriod.CURRENT)); + + return request; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelResponse.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelResponse.java new file mode 100644 index 000000000..a1ad62d66 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelResponse.java @@ -0,0 +1,23 @@ +package com.dianping.cat.report.page.model.spi; + +public class ModelResponse { + private Exception m_exception; + + private M m_model; + + public Exception getException() { + return m_exception; + } + + public M getModel() { + return m_model; + } + + public void setException(Exception exception) { + m_exception = exception; + } + + public void setModel(M model) { + m_model = model; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelService.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelService.java new file mode 100644 index 000000000..2ae7a0dda --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/spi/ModelService.java @@ -0,0 +1,7 @@ +package com.dianping.cat.report.page.model.spi; + +public interface ModelService { + public boolean isEligable(ModelRequest request); + + public ModelResponse invoke(ModelRequest request); +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/CompositeTransactionModelService.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/CompositeTransactionModelService.java new file mode 100644 index 000000000..c9afc42e6 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/CompositeTransactionModelService.java @@ -0,0 +1,97 @@ +package com.dianping.cat.report.page.model.transaction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable; +import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException; + +import com.dianping.cat.consumer.transaction.model.entity.TransactionReport; +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.site.lookup.annotation.Inject; + +public class CompositeTransactionModelService implements ModelService, Initializable { + @Inject + private List> m_services; + + private ExecutorService m_threadPool; + + @Override + public void initialize() throws InitializationException { + m_threadPool = Executors.newFixedThreadPool(10); + } + + @Override + public ModelResponse invoke(final ModelRequest request) { + int size = m_services.size(); + final List> responses = new ArrayList>(size); + final Semaphore semaphore = new Semaphore(0); + int count = 0; + + for (final ModelService service : m_services) { + if (service.isEligable(request)) { + m_threadPool.submit(new Runnable() { + @Override + public void run() { + try { + responses.add(service.invoke(request)); + } catch (Exception e) { + e.printStackTrace(); + } finally { + semaphore.release(); + } + } + }); + count++; + } + } + + try { + semaphore.tryAcquire(count, 5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // ignore it + } + + ModelResponse aggregated = new ModelResponse(); + TransactionReportMerger merger = null; + + for (ModelResponse response : responses) { + if (response != null) { + TransactionReport model = response.getModel(); + + if (model != null) { + if (merger == null) { + merger = new TransactionReportMerger(model); + } else { + model.accept(merger); + } + } + } + } + + aggregated.setModel(merger == null ? null : merger.getTransactionReport()); + return aggregated; + } + + @Override + public boolean isEligable(ModelRequest request) { + for (ModelService service : m_services) { + if (service.isEligable(request)) { + return true; + } + } + + return false; + } + + public void setSerivces(ModelService... services) { + m_services = Arrays.asList(services); + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/LocalTransactionModelService.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/LocalTransactionModelService.java new file mode 100644 index 000000000..43ed2ca8c --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/LocalTransactionModelService.java @@ -0,0 +1,57 @@ +package com.dianping.cat.report.page.model.transaction; + +import java.util.List; + +import com.dianping.cat.consumer.RealtimeConsumer; +import com.dianping.cat.consumer.transaction.TransactionAnalyzer; +import com.dianping.cat.consumer.transaction.model.entity.TransactionReport; +import com.dianping.cat.message.spi.MessageConsumer; +import com.dianping.cat.report.page.model.spi.ModelPeriod; +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.site.lookup.annotation.Inject; + +public class LocalTransactionModelService implements ModelService { + @Inject(type = MessageConsumer.class, value = "realtime") + private RealtimeConsumer m_consumer; + + @Override + public ModelResponse invoke(ModelRequest request) { + TransactionAnalyzer analyzer = getAnalyzer(request.getPeriod()); + ModelResponse response = new ModelResponse(); + + if (analyzer != null) { + List domains = analyzer.getDomains(); + String d = request.getDomain(); + TransactionReport report = analyzer.getReport(d != null ? d : domains.isEmpty() ? null : domains.get(0)); + + if (report != null) { + for (String domain : domains) { + report.addDomain(domain); + } + } + + response.setModel(report); + } + + return response; + } + + private TransactionAnalyzer getAnalyzer(ModelPeriod period) { + if (period.isCurrent()) { + return (TransactionAnalyzer) m_consumer.getCurrentAnalyzer("transaction"); + } else if (period.isLast()) { + return (TransactionAnalyzer) m_consumer.getLastAnalyzer("transaction"); + } else { + return null; + } + } + + @Override + public boolean isEligable(ModelRequest request) { + ModelPeriod period = request.getPeriod(); + + return period.isCurrent() || period.isLast(); + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/RemoteTransactionModelService.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/RemoteTransactionModelService.java new file mode 100644 index 000000000..eb4888052 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/RemoteTransactionModelService.java @@ -0,0 +1,81 @@ +package com.dianping.cat.report.page.model.transaction; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.Map.Entry; + +import com.dianping.cat.consumer.transaction.model.entity.TransactionReport; +import com.dianping.cat.consumer.transaction.model.transform.DefaultXmlParser; +import com.dianping.cat.report.page.model.spi.ModelPeriod; +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.site.helper.Files; +import com.site.helper.Joiners; +import com.site.helper.Joiners.IBuilder; +import com.site.lookup.annotation.Inject; + +public class RemoteTransactionModelService implements ModelService { + @Inject + private String m_host; + + @Inject + private int m_port = 2281; // default admin port + + @Inject + private String m_serviceUri = "/cat/r/model"; + + URL buildUrl(ModelRequest request) throws MalformedURLException { + String pairs = Joiners.by('&').prefixDelimiter() + .join(request.getProperties().entrySet(), new IBuilder>() { + @Override + public String asString(Entry e) { + return e.getKey() + "=" + e.getValue(); + } + }); + String url = String.format("http://%s:%s%s/%s/%s/%s?op=xml%s", m_host, m_port, m_serviceUri, "transaction", + request.getDomain(), request.getPeriod(), pairs); + + return new URL(url); + } + + @Override + public ModelResponse invoke(ModelRequest request) { + ModelResponse response = new ModelResponse(); + + try { + URL url = buildUrl(request); + String xml = Files.forIO().readFrom(url.openStream(), "utf-8"); + + if (xml != null && xml.trim().length() > 0) { + TransactionReport report = new DefaultXmlParser().parse(xml); + + response.setModel(report); + } + } catch (Exception e) { + response.setException(e); + } + + return response; + } + + @Override + public boolean isEligable(ModelRequest request) { + ModelPeriod period = request.getPeriod(); + + return period.isCurrent() || period.isLast(); + } + + public void setHost(String host) { + m_host = host; + } + + public void setPort(int port) { + m_port = port; + } + + public void setServiceUri(String serviceUri) { + m_serviceUri = serviceUri; + } +} diff --git a/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/TransactionReportMerger.java b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/TransactionReportMerger.java new file mode 100644 index 000000000..de8840a64 --- /dev/null +++ b/cat-home/src/main/java/com/dianping/cat/report/page/model/transaction/TransactionReportMerger.java @@ -0,0 +1,121 @@ +package com.dianping.cat.report.page.model.transaction; + +import java.util.List; + +import com.dianping.cat.consumer.transaction.model.entity.Duration; +import com.dianping.cat.consumer.transaction.model.entity.Range; +import com.dianping.cat.consumer.transaction.model.entity.TransactionName; +import com.dianping.cat.consumer.transaction.model.entity.TransactionReport; +import com.dianping.cat.consumer.transaction.model.entity.TransactionType; +import com.dianping.cat.consumer.transaction.model.transform.DefaultMerger; + +public class TransactionReportMerger extends DefaultMerger { + public TransactionReportMerger(TransactionReport transactionReport) { + super(transactionReport); + } + + public static TransactionReport merges(List reports) { + TransactionReportMerger merger = new TransactionReportMerger(new TransactionReport("")); + + for (TransactionReport report : reports) { + report.accept(merger); + } + + return merger.getTransactionReport(); + } + + @Override + protected void mergeDuration(Duration old, Duration duration) { + old.setCount(old.getCount() + duration.getCount()); + } + + @Override + protected void mergeName(TransactionName old, TransactionName other) { + old.setTotalCount(old.getTotalCount() + other.getTotalCount()); + old.setFailCount(old.getFailCount() + other.getFailCount()); + + if (other.getMin() < old.getMin()) { + old.setMin(other.getMin()); + } + + if (other.getMax() > old.getMax()) { + old.setMax(other.getMax()); + } + + old.setSum(old.getSum() + other.getSum()); + old.setSum2(old.getSum2() + other.getSum2()); + + if (old.getTotalCount() > 0) { + old.setFailPercent(old.getFailCount() * 100.0 / old.getTotalCount()); + old.setAvg(old.getSum() / old.getTotalCount()); + old.setStd(std(old.getTotalCount(), old.getAvg(), old.getSum2())); + } + + if (old.getSuccessMessageUrl() == null) { + old.setSuccessMessageUrl(other.getSuccessMessageUrl()); + } + + if (old.getFailMessageUrl() == null) { + old.setFailMessageUrl(other.getFailMessageUrl()); + } + } + + @Override + protected void mergeRange(Range old, Range range) { + old.setCount(old.getCount() + range.getCount()); + old.setFails(old.getFails() + range.getFails()); + old.setSum(old.getSum() + range.getSum()); + + if (old.getCount() > 0) { + old.setAvg(old.getSum() / old.getCount()); + } + } + + public TransactionReport mergesFrom(TransactionReport report) { + report.accept(this); + + return getTransactionReport(); + } + + @Override + protected void mergeTransactionReport(TransactionReport old, TransactionReport transactionReport) { + super.mergeTransactionReport(old, transactionReport); + + old.getDomains().addAll(transactionReport.getDomains()); + } + + @Override + protected void mergeType(TransactionType old, TransactionType other) { + old.setTotalCount(old.getTotalCount() + other.getTotalCount()); + old.setFailCount(old.getFailCount() + other.getFailCount()); + + if (other.getMin() < old.getMin()) { + old.setMin(other.getMin()); + } + + if (other.getMax() > old.getMax()) { + old.setMax(other.getMax()); + } + + old.setSum(old.getSum() + other.getSum()); + old.setSum2(old.getSum2() + other.getSum2()); + + if (old.getTotalCount() > 0) { + old.setFailPercent(old.getFailCount() * 100.0 / old.getTotalCount()); + old.setAvg(old.getSum() / old.getTotalCount()); + old.setStd(std(old.getTotalCount(), old.getAvg(), old.getSum2())); + } + + if (old.getSuccessMessageUrl() == null) { + old.setSuccessMessageUrl(other.getSuccessMessageUrl()); + } + + if (old.getFailMessageUrl() == null) { + old.setFailMessageUrl(other.getFailMessageUrl()); + } + } + + protected double std(long count, double ave, double sum2) { + return Math.sqrt(sum2 / count - 2 * ave * ave + ave * ave); + } +} diff --git a/cat-home/src/main/webapp/css/transaction.css b/cat-home/src/main/webapp/css/transaction.css new file mode 100644 index 000000000..7780ffa45 --- /dev/null +++ b/cat-home/src/main/webapp/css/transaction.css @@ -0,0 +1,50 @@ +.transaction { + width: auto; + font-size: small; +} + +.subtitle { + font-size: small; +} + +.timestamp { + font-size: small; +} + +.graph { + width: 400px; + height: 100px; +} + +tr.odd td { + background-color: #eee; + font-size: small; + white-space: nowrap; + vertical-align: top; +} + +tr.even td { + background-color: white; + font-size: small; + white-space: nowrap; + vertical-align: top; +} + +tr.link td { + font-size: small; + white-space: nowrap; + vertical-align: top; +} + +.current { + background-color: orange; + color: white; +} + +.warn { + color: yellow; +} + +.error { + color: red; +} \ No newline at end of file diff --git a/cat-home/src/main/webapp/jsp/report/model.jsp b/cat-home/src/main/webapp/jsp/report/model.jsp new file mode 100644 index 000000000..30c65b6f7 --- /dev/null +++ b/cat-home/src/main/webapp/jsp/report/model.jsp @@ -0,0 +1,5 @@ +<%@ page contentType="text/xml; charset=utf-8" trimDirectiveWhitespaces="true" %> + + + +${model.modelInXml} \ No newline at end of file diff --git a/cat-home/src/main/webapp/jsp/report/transaction_graph.jsp b/cat-home/src/main/webapp/jsp/report/transaction_graph.jsp new file mode 100644 index 000000000..ff3b173f2 --- /dev/null +++ b/cat-home/src/main/webapp/jsp/report/transaction_graph.jsp @@ -0,0 +1,46 @@ +<%@ page contentType="image/svg+xml; charset=utf-8"%> + + + Duration + + + + + + + + + 200 + 160 + 120 + 80 + 40 + 0 + + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + 18 + 20 + 22 + + + Bottom + Title + + + + 60 + + 30 + + 50 + + \ No newline at end of file diff --git a/cat-home/src/main/webapp/jsp/report/transaction_graphs.jsp b/cat-home/src/main/webapp/jsp/report/transaction_graphs.jsp new file mode 100644 index 000000000..23e7a8d1b --- /dev/null +++ b/cat-home/src/main/webapp/jsp/report/transaction_graphs.jsp @@ -0,0 +1,17 @@ +<%@ page contentType="text/html; charset=utf-8"%> + + + + +${model.graph} + +<%-- + + + + + + + + +
--%> \ No newline at end of file diff --git a/cat-home/src/test/java/com/dianping/cat/report/graph/ValueTranslaterTest.java b/cat-home/src/test/java/com/dianping/cat/report/graph/ValueTranslaterTest.java new file mode 100644 index 000000000..9c1623b23 --- /dev/null +++ b/cat-home/src/test/java/com/dianping/cat/report/graph/ValueTranslaterTest.java @@ -0,0 +1,20 @@ +package com.dianping.cat.report.graph; + +import junit.framework.Assert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import com.site.lookup.ComponentTestCase; + +@RunWith(JUnit4.class) +public class ValueTranslaterTest extends ComponentTestCase { + @Test + public void test() throws Exception { + ValueTranslater translater = lookup(ValueTranslater.class); + double[] values = { 123, 456, 247, 473, 976, 236 }; + + Assert.assertEquals(1000, translater.getMaxValue(values)); + } +} diff --git a/cat-home/src/test/java/com/dianping/cat/report/page/model/transaction/TransactionModelServiceTest.java b/cat-home/src/test/java/com/dianping/cat/report/page/model/transaction/TransactionModelServiceTest.java new file mode 100644 index 000000000..2474b3ba8 --- /dev/null +++ b/cat-home/src/test/java/com/dianping/cat/report/page/model/transaction/TransactionModelServiceTest.java @@ -0,0 +1,48 @@ +package com.dianping.cat.report.page.model.transaction; + +import junit.framework.Assert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import com.dianping.cat.report.page.model.spi.ModelRequest; +import com.dianping.cat.report.page.model.spi.ModelResponse; +import com.dianping.cat.report.page.model.spi.ModelService; +import com.site.lookup.ComponentTestCase; + +@RunWith(JUnit4.class) +public class TransactionModelServiceTest extends ComponentTestCase { + @Test + public void testLookup() throws Exception { + ModelService local = lookup(ModelService.class, "transaction-local"); + ModelService localhost = lookup(ModelService.class, "transaction-localhost"); + ModelService composite = lookup(ModelService.class, "transaction"); + + Assert.assertEquals(LocalTransactionModelService.class, local.getClass()); + Assert.assertEquals(RemoteTransactionModelService.class, localhost.getClass()); + Assert.assertEquals(CompositeTransactionModelService.class, composite.getClass()); + } + + @Test + public void testLocal() throws Exception { + LocalTransactionModelService local = (LocalTransactionModelService) lookup(ModelService.class, + "transaction-local"); + ModelResponse response = local.invoke(ModelRequest.from("Cat", "CURRENT")); + + Assert.assertEquals("null", String.valueOf(response.getModel())); // TODO try to mock up a real consumer for test + } + + @Test + public void testRemote() throws Exception { + RemoteTransactionModelService remote = (RemoteTransactionModelService) lookup(ModelService.class, + "transaction-localhost"); + ModelRequest request = ModelRequest.from("Cat", "CURRENT"); + + Assert.assertEquals("http://localhost:2281/cat/r/t/service?domain=Cat&period=CURRENT", remote.buildUrl(request).toString()); + + ModelResponse response = remote.invoke(request); + + Assert.assertEquals("null", String.valueOf(response.getModel())); // TODO start a test server, and do real stuff + } +} -- GitLab