hill9868 6 years ago
commit
c88ac49ec3
100 changed files with 10685 additions and 0 deletions
  1. 5 0
      .travis.yml
  2. 202 0
      LICENSE
  3. 55 0
      NOTICE.txt
  4. 378 0
      README.md
  5. 105 0
      cat-client/pom.xml
  6. 343 0
      cat-client/src/main/java/com/dianping/cat/Cat.java
  7. 89 0
      cat-client/src/main/java/com/dianping/cat/CatClientModule.java
  8. 83 0
      cat-client/src/main/java/com/dianping/cat/CatConstants.java
  9. 26 0
      cat-client/src/main/java/com/dianping/cat/build/CodecComponentConfigurator.java
  10. 61 0
      cat-client/src/main/java/com/dianping/cat/build/ComponentsConfigurator.java
  11. 27 0
      cat-client/src/main/java/com/dianping/cat/configuration/ClientConfigManager.java
  12. 71 0
      cat-client/src/main/java/com/dianping/cat/configuration/ClientConfigMerger.java
  13. 78 0
      cat-client/src/main/java/com/dianping/cat/configuration/ClientConfigValidator.java
  14. 247 0
      cat-client/src/main/java/com/dianping/cat/configuration/DefaultClientConfigManager.java
  15. 26 0
      cat-client/src/main/java/com/dianping/cat/configuration/KVConfig.java
  16. 18 0
      cat-client/src/main/java/com/dianping/cat/configuration/NetworkInterfaceManager.java
  17. 81 0
      cat-client/src/main/java/com/dianping/cat/log4j/CatAppender.java
  18. 74 0
      cat-client/src/main/java/com/dianping/cat/log4j/Log4j2Appender.java
  19. 40 0
      cat-client/src/main/java/com/dianping/cat/logback/CatLogbackAppender.java
  20. 24 0
      cat-client/src/main/java/com/dianping/cat/message/Event.java
  21. 26 0
      cat-client/src/main/java/com/dianping/cat/message/Heartbeat.java
  22. 112 0
      cat-client/src/main/java/com/dianping/cat/message/Message.java
  23. 274 0
      cat-client/src/main/java/com/dianping/cat/message/MessageProducer.java
  24. 24 0
      cat-client/src/main/java/com/dianping/cat/message/Metric.java
  25. 18 0
      cat-client/src/main/java/com/dianping/cat/message/Trace.java
  26. 81 0
      cat-client/src/main/java/com/dianping/cat/message/Transaction.java
  27. 137 0
      cat-client/src/main/java/com/dianping/cat/message/internal/AbstractMessage.java
  28. 27 0
      cat-client/src/main/java/com/dianping/cat/message/internal/DefaultEvent.java
  29. 27 0
      cat-client/src/main/java/com/dianping/cat/message/internal/DefaultHeartbeat.java
  30. 531 0
      cat-client/src/main/java/com/dianping/cat/message/internal/DefaultMessageManager.java
  31. 246 0
      cat-client/src/main/java/com/dianping/cat/message/internal/DefaultMessageProducer.java
  32. 27 0
      cat-client/src/main/java/com/dianping/cat/message/internal/DefaultMetric.java
  33. 27 0
      cat-client/src/main/java/com/dianping/cat/message/internal/DefaultTrace.java
  34. 137 0
      cat-client/src/main/java/com/dianping/cat/message/internal/DefaultTransaction.java
  35. 129 0
      cat-client/src/main/java/com/dianping/cat/message/internal/MessageId.java
  36. 176 0
      cat-client/src/main/java/com/dianping/cat/message/internal/MessageIdFactory.java
  37. 52 0
      cat-client/src/main/java/com/dianping/cat/message/internal/MilliSecondTimer.java
  38. 356 0
      cat-client/src/main/java/com/dianping/cat/message/internal/MockMessageBuilder.java
  39. 116 0
      cat-client/src/main/java/com/dianping/cat/message/internal/NullMessage.java
  40. 470 0
      cat-client/src/main/java/com/dianping/cat/message/io/ChannelManager.java
  41. 57 0
      cat-client/src/main/java/com/dianping/cat/message/io/DefaultMessageQueue.java
  42. 62 0
      cat-client/src/main/java/com/dianping/cat/message/io/DefaultTransportManager.java
  43. 11 0
      cat-client/src/main/java/com/dianping/cat/message/io/MessageSender.java
  44. 321 0
      cat-client/src/main/java/com/dianping/cat/message/io/TcpSocketSender.java
  45. 5 0
      cat-client/src/main/java/com/dianping/cat/message/io/TransportManager.java
  46. 11 0
      cat-client/src/main/java/com/dianping/cat/message/spi/MessageCodec.java
  47. 96 0
      cat-client/src/main/java/com/dianping/cat/message/spi/MessageManager.java
  48. 14 0
      cat-client/src/main/java/com/dianping/cat/message/spi/MessageQueue.java
  49. 14 0
      cat-client/src/main/java/com/dianping/cat/message/spi/MessageStatistics.java
  50. 55 0
      cat-client/src/main/java/com/dianping/cat/message/spi/MessageTree.java
  51. 7 0
      cat-client/src/main/java/com/dianping/cat/message/spi/codec/BufferWriter.java
  52. 42 0
      cat-client/src/main/java/com/dianping/cat/message/spi/codec/EscapingBufferWriter.java
  53. 609 0
      cat-client/src/main/java/com/dianping/cat/message/spi/codec/PlainTextMessageCodec.java
  54. 38 0
      cat-client/src/main/java/com/dianping/cat/message/spi/internal/DefaultMessageStatistics.java
  55. 205 0
      cat-client/src/main/java/com/dianping/cat/message/spi/internal/DefaultMessageTree.java
  56. 349 0
      cat-client/src/main/java/com/dianping/cat/servlet/CatFilter.java
  57. 37 0
      cat-client/src/main/java/com/dianping/cat/status/HeartbeatExtenstion.java
  58. 12 0
      cat-client/src/main/java/com/dianping/cat/status/StatusExtension.java
  59. 36 0
      cat-client/src/main/java/com/dianping/cat/status/StatusExtensionRegister.java
  60. 287 0
      cat-client/src/main/java/com/dianping/cat/status/StatusInfoCollector.java
  61. 193 0
      cat-client/src/main/java/com/dianping/cat/status/StatusUpdateTask.java
  62. 30 0
      cat-client/src/main/java/com/dianping/cat/utils/HttpRequestUtils.java
  63. 77 0
      cat-client/src/main/java/com/site/helper/JsonBuilder.java
  64. 124 0
      cat-client/src/main/java/com/site/helper/Splitters.java
  65. 309 0
      cat-client/src/main/java/com/site/helper/Stringizers.java
  66. 100 0
      cat-client/src/main/java/com/site/lookup/util/StringUtils.java
  67. 33 0
      cat-client/src/main/resources/META-INF/dal/model/client-codegen.xml
  68. 6 0
      cat-client/src/main/resources/META-INF/dal/model/client-manifest.xml
  69. 26 0
      cat-client/src/main/resources/META-INF/dal/model/client-model.xml
  70. 65 0
      cat-client/src/main/resources/META-INF/dal/model/status-codegen.xml
  71. 6 0
      cat-client/src/main/resources/META-INF/dal/model/status-manifest.xml
  72. 72 0
      cat-client/src/main/resources/META-INF/dal/model/status-model.xml
  73. 111 0
      cat-client/src/main/resources/META-INF/plexus/components-cat-client.xml
  74. 15 0
      cat-client/src/main/resources/META-INF/plexus/plexus.xml
  75. 5 0
      cat-client/src/main/resources/META-INF/wizard/model/manifest.xml
  76. 3 0
      cat-client/src/main/resources/META-INF/wizard/model/wizard.xml
  77. 49 0
      cat-client/src/test/java/com/dianping/cat/AllTests.java
  78. 64 0
      cat-client/src/test/java/com/dianping/cat/ApiTest.java
  79. 71 0
      cat-client/src/test/java/com/dianping/cat/CatEnvironmentTest.java
  80. 35 0
      cat-client/src/test/java/com/dianping/cat/CatTest.java
  81. 22 0
      cat-client/src/test/java/com/dianping/cat/MessageFomatTest.java
  82. 123 0
      cat-client/src/test/java/com/dianping/cat/ToolsTest.java
  83. 93 0
      cat-client/src/test/java/com/dianping/cat/agent/MmapConsumerTaskTest.java
  84. 58 0
      cat-client/src/test/java/com/dianping/cat/configuration/ConfigTest.java
  85. 87 0
      cat-client/src/test/java/com/dianping/cat/message/AppSimulator.java
  86. 226 0
      cat-client/src/test/java/com/dianping/cat/message/CatPerformanceTest.java
  87. 62 0
      cat-client/src/test/java/com/dianping/cat/message/CatTestCase.java
  88. 34 0
      cat-client/src/test/java/com/dianping/cat/message/EventTest.java
  89. 43 0
      cat-client/src/test/java/com/dianping/cat/message/HeartbeatTest.java
  90. 324 0
      cat-client/src/test/java/com/dianping/cat/message/MessageTest.java
  91. 20 0
      cat-client/src/test/java/com/dianping/cat/message/MetricTest.java
  92. 29 0
      cat-client/src/test/java/com/dianping/cat/message/TransactionTest.java
  93. 104 0
      cat-client/src/test/java/com/dianping/cat/message/internal/CatClientTest.java
  94. 171 0
      cat-client/src/test/java/com/dianping/cat/message/internal/MessageIdFactoryTest.java
  95. 147 0
      cat-client/src/test/java/com/dianping/cat/message/internal/MessageProducerTest.java
  96. 72 0
      cat-client/src/test/java/com/dianping/cat/message/internal/MockMessageBuilderTest.java
  97. 82 0
      cat-client/src/test/java/com/dianping/cat/message/spi/codec/MessageCodecPerformanceTest.java
  98. 76 0
      cat-client/src/test/java/com/dianping/cat/message/spi/codec/MessageCodecUnexceptedCharTest.java
  99. 224 0
      cat-client/src/test/java/com/dianping/cat/message/spi/codec/PlainTextMessageCodecTest.java
  100. 0 0
      cat-client/src/test/java/com/dianping/cat/message/spi/codec/PlainTextMessageCodecTestConfigurator.java

+ 5 - 0
.travis.yml

@ -0,0 +1,5 @@
language: java
install:
  mvn install -Dmaven.test.skip -B -fae
script:
  mvn test -B -fae

+ 202 - 0
LICENSE

@ -0,0 +1,202 @@
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
   1. Definitions.
      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.
      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.
      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.
      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.
      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.
      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.
      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).
      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.
      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."
      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.
   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.
   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.
   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:
      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and
      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and
      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and
      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.
      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.
   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.
   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.
   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.
   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.
   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.
   END OF TERMS AND CONDITIONS
   APPENDIX: How to apply the Apache License to your work.
      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.
   Copyright [yyyy] [name of copyright owner]
   Licensed 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.

+ 55 - 0
NOTICE.txt

@ -0,0 +1,55 @@
Dianping CAT
Copyright 2013, DianPing Inc.
Third party libraries
---------------------
This product includes software developed by The Apache Software
Foundation (http://www.apache.org/),including, but not limit to:
  - Apache Hadoop
  - Apache Commons
  - Apache Maven
  - Apache log4j
This product includes the several frameworks developped by 
unidal.org (https://github.com/unidal/),including:
  - maven-plugins (https://github.com/unidal/maven-plugins)
  - frameworks (https://github.com/unidal/frameworks)
Copyright 2011-2013 unidal.org
This product includes the Jetty HTTP server
(http://jetty.codehaus.org/jetty/).
Copyright 1995-2006 Mort Bay Consulting Pty Ltd
Netty
(https://netty.io/)
Copyright (C) 2011 The Netty Project
MySQL Connector/J
(http://www.mysql.com)
distributed under GPL v2 license with MySQL FOSS exception (LICENSE-MYSQL)
Copyright (c) 2000, 2011, Oracle and/or its affiliates
Gson
(http://code.google.com/p/google-gson)
distributed under the Apache Software License, Version 2.0.
Copyright (c) 2010 Google Inc.
javaparser
(https://code.google.com/p/javaparser/)
distributed under GNU Lesser GPL
freemarker
(http://freemarker.org/)
distributed under BSD-style license
cobar
(http://code.alibabatech.com/wiki/display/cobar/Home)
Copyright (c) 2010 Alibaba Inc.
JUnit 
(http://www.junit.org/) 
distributed under the Common Public License v1.0
https://www.cnblogs.com/grasshopper/p/7098987.html

File diff suppressed because it is too large
+ 378 - 0
README.md


+ 105 - 0
cat-client/pom.xml

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<parent>
		<groupId>com.dianping.cat</groupId>
		<artifactId>parent</artifactId>
		<version>2.0.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>cat-client</artifactId>
	<name>cat-client</name>
	<packaging>jar</packaging>
	<dependencies>
		<dependency>
			<groupId>org.unidal.framework</groupId>
			<artifactId>foundation-service</artifactId>
		</dependency>
		<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
		</dependency>
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<optional>true</optional>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
			<optional>true</optional>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<optional>true</optional>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<optional>true</optional>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.unidal.framework</groupId>
			<artifactId>test-framework</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.unidal.maven.plugins</groupId>
				<artifactId>codegen-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>generate data model</id>
						<phase>generate-sources</phase>
						<goals>
							<goal>dal-model</goal>
						</goals>
						<configuration>
							<manifest>
								${basedir}/src/main/resources/META-INF/dal/model/client-manifest.xml,
								${basedir}/src/main/resources/META-INF/dal/model/status-manifest.xml,
							</manifest>
						</configuration>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.unidal.maven.plugins</groupId>
				<artifactId>plexus-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>generate plexus component descriptor</id>
						<phase>process-classes</phase>
						<goals>
							<goal>plexus</goal>
						</goals>
						<configuration>
							<className>com.dianping.cat.build.ComponentsConfigurator</className>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

+ 343 - 0
cat-client/src/main/java/com/dianping/cat/Cat.java

@ -0,0 +1,343 @@
package com.dianping.cat;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Date;
import org.unidal.helper.Files;
import org.unidal.helper.Properties;
import org.unidal.initialization.DefaultModuleContext;
import org.unidal.initialization.Module;
import org.unidal.initialization.ModuleContext;
import org.unidal.initialization.ModuleInitializer;
import org.unidal.lookup.ComponentLookupException;
import org.unidal.lookup.ContainerLoader;
import org.unidal.lookup.PlexusContainer;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Server;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.MessageProducer;
import com.dianping.cat.message.Trace;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.spi.MessageManager;
import com.dianping.cat.message.spi.MessageTree;
/**
 * This is the main entry point to the system.
 */
public class Cat {
	private static Cat s_instance = new Cat();
	private static volatile boolean s_init = false;
	private MessageProducer m_producer;
	private MessageManager m_manager;
	private PlexusContainer m_container;
	private static void checkAndInitialize() {
		if (!s_init) {
			synchronized (s_instance) {
				if (!s_init) {
					initialize(new File(getCatHome(), "client.xml"));
					log("WARN", "Cat is lazy initialized!");
					s_init = true;
				}
			}
		}
	}
	public static String createMessageId() {
		return Cat.getProducer().createMessageId();
	}
	public static void destroy() {
		s_instance.m_container.dispose();
		s_instance = new Cat();
	}
	public static String getCatHome() {
		String catHome = Properties.forString().fromEnv().fromSystem().getProperty("CAT_HOME", "/data/appdatas/cat");
		return catHome;
	}
	public static String getCurrentMessageId() {
		MessageTree tree = Cat.getManager().getThreadLocalMessageTree();
		if (tree != null) {
			String messageId = tree.getMessageId();
			if (messageId == null) {
				messageId = Cat.createMessageId();
				tree.setMessageId(messageId);
			}
			return messageId;
		} else {
			return null;
		}
	}
	public static Cat getInstance() {
		return s_instance;
	}
	public static MessageManager getManager() {
		checkAndInitialize();
		return s_instance.m_manager;
	}
	public static MessageProducer getProducer() {
		checkAndInitialize();
		return s_instance.m_producer;
	}
	// this should be called during application initialization time
	public static void initialize(File configFile) {
		PlexusContainer container = ContainerLoader.getDefaultContainer();
		initialize(container, configFile);
	}
	public static void initialize(PlexusContainer container, File configFile) {
		ModuleContext ctx = new DefaultModuleContext(container);
		Module module = ctx.lookup(Module.class, CatClientModule.ID);
		if (!module.isInitialized()) {
			ModuleInitializer initializer = ctx.lookup(ModuleInitializer.class);
			ctx.setAttribute("cat-client-config-file", configFile);
			initializer.execute(ctx, module);
		}
	}
	public static void initialize(String... servers) {
		File configFile = null;
		try {
			configFile = File.createTempFile("cat-client", ".xml");
			ClientConfig config = new ClientConfig().setMode("client");
			for (String server : servers) {
				config.addServer(new Server(server));
			}
			Files.forIO().writeTo(configFile, config.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
		initialize(configFile);
	}
	public static boolean isInitialized() {
		return s_init;
	}
	static void log(String severity, String message) {
		MessageFormat format = new MessageFormat("[{0,date,MM-dd HH:mm:ss.sss}] [{1}] [{2}] {3}");
		System.out.println(format.format(new Object[] { new Date(), severity, "cat", message }));
	}
	public static void logError(String message, Throwable cause) {
		Cat.getProducer().logError(message, cause);
	}
	public static void logError(Throwable cause) {
		Cat.getProducer().logError(cause);
	}
	public static void logEvent(String type, String name) {
		Cat.getProducer().logEvent(type, name);
	}
	public static void logEvent(String type, String name, String status, String nameValuePairs) {
		Cat.getProducer().logEvent(type, name, status, nameValuePairs);
	}
	public static void logHeartbeat(String type, String name, String status, String nameValuePairs) {
		Cat.getProducer().logHeartbeat(type, name, status, nameValuePairs);
	}
	public static void logMetric(String name, Object... keyValues) {
		// TO REMOVE ME
	}
	/**
	 * Increase the counter specified by <code>name</code> by one.
	 * 
	 * @param name
	 *           the name of the metric default count value is 1
	 */
	public static void logMetricForCount(String name) {
		logMetricInternal(name, "C", "1");
	}
	/**
	 * Increase the counter specified by <code>name</code> by one.
	 * 
	 * @param name
	 *           the name of the metric
	 */
	public static void logMetricForCount(String name, int quantity) {
		logMetricInternal(name, "C", String.valueOf(quantity));
	}
	/**
	 * Increase the metric specified by <code>name</code> by <code>durationInMillis</code>.
	 * 
	 * @param name
	 *           the name of the metric
	 * @param durationInMillis
	 *           duration in milli-second added to the metric
	 */
	public static void logMetricForDuration(String name, long durationInMillis) {
		logMetricInternal(name, "T", String.valueOf(durationInMillis));
	}
	/**
	 * Increase the sum specified by <code>name</code> by <code>value</code> only for one item.
	 * 
	 * @param name
	 *           the name of the metric
	 * @param value
	 *           the value added to the metric
	 */
	public static void logMetricForSum(String name, double value) {
		logMetricInternal(name, "S", String.format("%.2f", value));
	}
	/**
	 * Increase the metric specified by <code>name</code> by <code>sum</code> for multiple items.
	 * 
	 * @param name
	 *           the name of the metric
	 * @param sum
	 *           the sum value added to the metric
	 * @param quantity
	 *           the quantity to be accumulated
	 */
	public static void logMetricForSum(String name, double sum, int quantity) {
		logMetricInternal(name, "S,C", String.format("%s,%.2f", quantity, sum));
	}
	private static void logMetricInternal(String name, String status, String keyValuePairs) {
		Cat.getProducer().logMetric(name, status, keyValuePairs);
	}
	public static void logRemoteCallClient(Context ctx) {
		MessageTree tree = Cat.getManager().getThreadLocalMessageTree();
		String messageId = tree.getMessageId();
		if (messageId == null) {
			messageId = Cat.createMessageId();
			tree.setMessageId(messageId);
		}
		String childId = Cat.createMessageId();
		Cat.logEvent(CatConstants.TYPE_REMOTE_CALL, "", Event.SUCCESS, childId);
		String root = tree.getRootMessageId();
		if (root == null) {
			root = messageId;
		}
		ctx.addProperty(Context.ROOT, root);
		ctx.addProperty(Context.PARENT, messageId);
		ctx.addProperty(Context.CHILD, childId);
	}
	public static void logRemoteCallServer(Context ctx) {
		MessageTree tree = Cat.getManager().getThreadLocalMessageTree();
		String messageId = ctx.getProperty(Context.CHILD);
		String rootId = ctx.getProperty(Context.ROOT);
		String parentId = ctx.getProperty(Context.PARENT);
		if (messageId != null) {
			tree.setMessageId(messageId);
		}
		if (parentId != null) {
			tree.setParentMessageId(parentId);
		}
		if (rootId != null) {
			tree.setRootMessageId(rootId);
		}
	}
	public static void logTrace(String type, String name) {
		Cat.getProducer().logTrace(type, name);
	}
	public static void logTrace(String type, String name, String status, String nameValuePairs) {
		Cat.getProducer().logTrace(type, name, status, nameValuePairs);
	}
	public static <T> T lookup(Class<T> role) throws ComponentLookupException {
		return lookup(role, null);
	}
	public static <T> T lookup(Class<T> role, String hint) throws ComponentLookupException {
		return s_instance.m_container.lookup(role, hint);
	}
	public static Event newEvent(String type, String name) {
		return Cat.getProducer().newEvent(type, name);
	}
	public static Heartbeat newHeartbeat(String type, String name) {
		return Cat.getProducer().newHeartbeat(type, name);
	}
	public static Trace newTrace(String type, String name) {
		return Cat.getProducer().newTrace(type, name);
	}
	public static Transaction newTransaction(String type, String name) {
		return Cat.getProducer().newTransaction(type, name);
	}
	// this should be called when a thread ends to clean some thread local data
	public static void reset() {
		// remove me
	}
	// this should be called when a thread starts to create some thread local data
	public static void setup(String sessionToken) {
		Cat.getManager().setup();
	}
	private Cat() {
	}
	void setContainer(PlexusContainer container) {
		try {
			m_container = container;
			m_manager = container.lookup(MessageManager.class);
			m_producer = container.lookup(MessageProducer.class);
		} catch (ComponentLookupException e) {
			throw new RuntimeException("Unable to get instance of MessageManager, "
			      + "please make sure the environment was setup correctly!", e);
		}
	}
	public static interface Context {
		public final String ROOT = "_catRootMessageId";
		public final String PARENT = "_catParentMessageId";
		public final String CHILD = "_catChildMessageId";
		public void addProperty(String key, String value);
		public String getProperty(String key);
	}
}

+ 89 - 0
cat-client/src/main/java/com/dianping/cat/CatClientModule.java

@ -0,0 +1,89 @@
package com.dianping.cat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.LockSupport;
import org.unidal.helper.Threads;
import org.unidal.helper.Threads.AbstractThreadListener;
import org.unidal.initialization.AbstractModule;
import org.unidal.initialization.DefaultModuleContext;
import org.unidal.initialization.Module;
import org.unidal.initialization.ModuleContext;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.message.internal.MilliSecondTimer;
import com.dianping.cat.message.io.TransportManager;
import com.dianping.cat.status.StatusUpdateTask;
public class CatClientModule extends AbstractModule {
	public static final String ID = "cat-client";
	@Override
	protected void execute(final ModuleContext ctx) throws Exception {
		ctx.info("Current working directory is " + System.getProperty("user.dir"));
		// initialize milli-second resolution level timer
		MilliSecondTimer.initialize();
		// tracking thread start/stop
		Threads.addListener(new CatThreadListener(ctx));
		// warm up Cat
		Cat.getInstance().setContainer(((DefaultModuleContext) ctx).getContainer());
		// bring up TransportManager
		ctx.lookup(TransportManager.class);
		ClientConfigManager clientConfigManager = ctx.lookup(ClientConfigManager.class);
		
		if (clientConfigManager.isCatEnabled()) {
			// start status update task
			StatusUpdateTask statusUpdateTask = ctx.lookup(StatusUpdateTask.class);
			Threads.forGroup("cat").start(statusUpdateTask);
			LockSupport.parkNanos(10 * 1000 * 1000L); // wait 10 ms
			// MmapConsumerTask mmapReaderTask = ctx.lookup(MmapConsumerTask.class);
			// Threads.forGroup("cat").start(mmapReaderTask);
		}
	}
	@Override
	public Module[] getDependencies(ModuleContext ctx) {
		return null; // no dependencies
	}
	public static final class CatThreadListener extends AbstractThreadListener {
		private final ModuleContext m_ctx;
		private CatThreadListener(ModuleContext ctx) {
			m_ctx = ctx;
		}
		@Override
		public void onThreadGroupCreated(ThreadGroup group, String name) {
			m_ctx.info(String.format("Thread group(%s) created.", name));
		}
		@Override
		public void onThreadPoolCreated(ExecutorService pool, String name) {
			m_ctx.info(String.format("Thread pool(%s) created.", name));
		}
		@Override
		public void onThreadStarting(Thread thread, String name) {
			m_ctx.info(String.format("Starting thread(%s) ...", name));
		}
		@Override
		public void onThreadStopping(Thread thread, String name) {
			m_ctx.info(String.format("Stopping thread(%s).", name));
		}
		@Override
		public boolean onUncaughtException(Thread thread, Throwable e) {
			m_ctx.error(String.format("Uncaught exception thrown out of thread(%s)", thread.getName()), e);
			return true;
		}
	}
}

+ 83 - 0
cat-client/src/main/java/com/dianping/cat/CatConstants.java

@ -0,0 +1,83 @@
package com.dianping.cat;
public class CatConstants {
	/**
	 * Cat Json length
	 */
	public static final int MAX_LENGTH = 1000;
	public static final int MAX_ITEM_LENGTH = 50;
	/**
	 * Cat instrument attribute names
	 */
	public static final String CAT_STATE = "cat-state";
	public static final String CAT_PAGE_URI = "cat-page-uri";
	public static final String CAT_PAGE_TYPE = "cat-page-type";
	/**
	 * Pigeon Transation Type
	 */
	public static final String TYPE_CALL = "Call";
	public static final String TYPE_RESULT = "Result";
	public static final String TYPE_TimeOut = "PigeonTimeOut";
	public static final String TYPE_SERVICE = "Service";
	public static final String TYPE_REMOTE_CALL = "RemoteCall";
	public static final String TYPE_REQUEST = "Request";
	public static final String TYPE_RESPONSE = "Respone";
	/**
	 * Pigeon Event Type, it is used to record the param
	 */
	public static final String TYPE_PIGEON_REQUEST = "PigeonRequest";
	public static final String TYPE_PIGEON_RESPONSE = "PigeonRespone";
	/**
	 * Pigeon Event name
	 */
	public static final String NAME_REQUEST = "PigeonRequest";
	public static final String NAME_RESPONSE = "PigeonRespone";
	public static final String NAME_TIME_OUT = "ClientTimeOut";
	/**
	 * Pigeon Context Info
	 */
	public static final String PIGEON_ROOT_MESSAGE_ID = "RootMessageId";
	public static final String PIGEON_CURRENT_MESSAGE_ID = "CurrentMessageId";
	public static final String PIGEON_SERVER_MESSAGE_ID = "ServerMessageId";
	public static final String PIGEON_RESPONSE_MESSAGE_ID = "ResponseMessageId";
	public static final String TYPE_SQL = "SQL";
	public static final String TYPE_SQL_PARAM = "SQL.PARAM";
	public static final String TYPE_URL = "URL";
	public static final String TYPE_URL_FORWARD = "URL.Forward";
	public static final String TYPE_ACTION = "Action";
	public static final String TYPE_METRIC = "MetricType";
	public static final String TYPE_TRACE = "TraceMode";
	public static final int ERROR_COUNT = 100;
	public static final int SUCCESS_COUNT = 1000;
}

+ 26 - 0
cat-client/src/main/java/com/dianping/cat/build/CodecComponentConfigurator.java

@ -0,0 +1,26 @@
package com.dianping.cat.build;
import java.util.ArrayList;
import java.util.List;
import org.unidal.lookup.configuration.AbstractResourceConfigurator;
import org.unidal.lookup.configuration.Component;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.codec.BufferWriter;
import com.dianping.cat.message.spi.codec.EscapingBufferWriter;
import com.dianping.cat.message.spi.codec.PlainTextMessageCodec;
class CodecComponentConfigurator extends AbstractResourceConfigurator {
	@Override
	public List<Component> defineComponents() {
		List<Component> all = new ArrayList<Component>();
		all.add(C(BufferWriter.class, EscapingBufferWriter.ID, EscapingBufferWriter.class));
		all.add(C(MessageCodec.class, PlainTextMessageCodec.ID, PlainTextMessageCodec.class) //
		      .req(BufferWriter.class, EscapingBufferWriter.ID));
		return all;
	}
}

+ 61 - 0
cat-client/src/main/java/com/dianping/cat/build/ComponentsConfigurator.java

@ -0,0 +1,61 @@
package com.dianping.cat.build;
import java.util.ArrayList;
import java.util.List;
import org.unidal.initialization.Module;
import org.unidal.lookup.configuration.AbstractResourceConfigurator;
import org.unidal.lookup.configuration.Component;
import com.dianping.cat.CatClientModule;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.configuration.DefaultClientConfigManager;
import com.dianping.cat.message.MessageProducer;
import com.dianping.cat.message.internal.DefaultMessageManager;
import com.dianping.cat.message.internal.DefaultMessageProducer;
import com.dianping.cat.message.internal.MessageIdFactory;
import com.dianping.cat.message.io.DefaultTransportManager;
import com.dianping.cat.message.io.TcpSocketSender;
import com.dianping.cat.message.io.TransportManager;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.MessageManager;
import com.dianping.cat.message.spi.MessageStatistics;
import com.dianping.cat.message.spi.codec.PlainTextMessageCodec;
import com.dianping.cat.message.spi.internal.DefaultMessageStatistics;
import com.dianping.cat.status.StatusUpdateTask;
public class ComponentsConfigurator extends AbstractResourceConfigurator {
	public static void main(String[] args) {
		generatePlexusComponentsXmlFile(new ComponentsConfigurator());
	}
	@Override
	public List<Component> defineComponents() {
		List<Component> all = new ArrayList<Component>();
		all.add(C(ClientConfigManager.class, DefaultClientConfigManager.class));
		all.add(C(MessageIdFactory.class));
		all.add(C(MessageManager.class, DefaultMessageManager.class) //
		      .req(ClientConfigManager.class, TransportManager.class,  MessageIdFactory.class));
		all.add(C(MessageProducer.class, DefaultMessageProducer.class) //
		      .req(MessageManager.class, MessageIdFactory.class));
		all.add(C(TcpSocketSender.class) //
		      .req(ClientConfigManager.class, MessageIdFactory.class) //
		      .req(MessageStatistics.class, "default", "m_statistics") //
		      .req(MessageCodec.class, PlainTextMessageCodec.ID, "m_codec"));
		all.add(C(TransportManager.class, DefaultTransportManager.class) //
		      .req(ClientConfigManager.class, TcpSocketSender.class));
		all.add(C(MessageStatistics.class, DefaultMessageStatistics.class));
		all.add(C(StatusUpdateTask.class) //
		      .req(MessageStatistics.class, ClientConfigManager.class));
		all.add(C(Module.class, CatClientModule.ID, CatClientModule.class));
		all.addAll(new CodecComponentConfigurator().defineComponents());
		return all;
	}
}

+ 27 - 0
cat-client/src/main/java/com/dianping/cat/configuration/ClientConfigManager.java

@ -0,0 +1,27 @@
package com.dianping.cat.configuration;
import java.io.File;
import java.util.List;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.configuration.client.entity.Server;
public interface ClientConfigManager {
	public Domain getDomain();
	public int getMaxMessageLength();
	public String getServerConfigUrl();
	public List<Server> getServers();
	public int getTaggedTransactionCacheSize();
	public void initialize(File configFile) throws Exception;
	public boolean isCatEnabled();
	public boolean isDumpLocked();
}

+ 71 - 0
cat-client/src/main/java/com/dianping/cat/configuration/ClientConfigMerger.java

@ -0,0 +1,71 @@
package com.dianping.cat.configuration;
import java.util.Stack;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.configuration.client.entity.Property;
import com.dianping.cat.configuration.client.transform.DefaultMerger;
public class ClientConfigMerger extends DefaultMerger {
	public ClientConfigMerger(ClientConfig config) {
		super(config);
	}
	@Override
	protected void mergeDomain(Domain old, Domain domain) {
		if (domain.getIp() != null) {
			old.setIp(domain.getIp());
		}
		if (domain.getEnabled() != null) {
			old.setEnabled(domain.getEnabled());
		}
		if (domain.getMaxMessageSize() > 0) {
			old.setMaxMessageSize(domain.getMaxMessageSize());
		}
	}
	@Override
	protected void visitConfigChildren(ClientConfig to, ClientConfig from) {
		if (to != null) {
			Stack<Object> objs = getObjects();
			// if servers is configured, then override it instead of merge
			if (!from.getServers().isEmpty()) {
				to.getServers().clear();
				to.getServers().addAll(from.getServers());
			}
			// only configured domain in client configure will be merged
			for (Domain source : from.getDomains().values()) {
				Domain target = to.findDomain(source.getId());
				if (target == null) {
					target = new Domain(source.getId());
					to.addDomain(target);
				}
				if (to.getDomains().containsKey(source.getId())) {
					objs.push(target);
					source.accept(this);
					objs.pop();
				}
			}
			for (Property source : from.getProperties().values()) {
				Property target = to.findProperty(source.getName());
				if (target == null) {
					target = new Property(source.getName());
					to.addProperty(target);
				}
				objs.push(target);
				source.accept(this);
				objs.pop();
			}
		}
	}
}

+ 78 - 0
cat-client/src/main/java/com/dianping/cat/configuration/ClientConfigValidator.java

@ -0,0 +1,78 @@
package com.dianping.cat.configuration;
import java.text.MessageFormat;
import java.util.Date;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.configuration.client.entity.Server;
import com.dianping.cat.configuration.client.transform.DefaultValidator;
public class ClientConfigValidator extends DefaultValidator {
	private ClientConfig m_config;
	private String getLocalAddress() {
		return NetworkInterfaceManager.INSTANCE.getLocalHostAddress();
	}
	private void log(String severity, String message) {
		MessageFormat format = new MessageFormat("[{0,date,MM-dd HH:mm:ss.sss}] [{1}] [{2}] {3}");
		System.out.println(format.format(new Object[] { new Date(), severity, "cat", message }));
	}
	@Override
	public void visitConfig(ClientConfig config) {
		config.setMode("client");
		if (config.getServers().size() == 0) {
			config.setEnabled(false);
			log("WARN", "CAT client was disabled due to no CAT servers configured!");
		} else if (!config.isEnabled()) {
			log("WARN", "CAT client was globally disabled!");
		}
		m_config = config;
		super.visitConfig(config);
		if (m_config.isEnabled()) {
			for (Domain domain : m_config.getDomains().values()) {
				if (!domain.isEnabled()) {
					m_config.setEnabled(false);
					log("WARN", "CAT client was disabled in domain(" + domain.getId() + ") explicitly!");
				}
				break; // for first domain only
			}
		}
	}
	@Override
	public void visitDomain(Domain domain) {
		super.visitDomain(domain);
		// set default values
		if (domain.getEnabled() == null) {
			domain.setEnabled(true);
		}
		if (domain.getIp() == null) {
			domain.setIp(getLocalAddress());
		}
	}
	@Override
	public void visitServer(Server server) {
		super.visitServer(server);
		// set default values
		if (server.getPort() == null) {
			server.setPort(2280);
		}
		if (server.getEnabled() == null) {
			server.setEnabled(true);
		}
	}
}

+ 247 - 0
cat-client/src/main/java/com/dianping/cat/configuration/DefaultClientConfigManager.java

@ -0,0 +1,247 @@
package com.dianping.cat.configuration;
import java.io.File;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.unidal.helper.Files;
import org.unidal.lookup.extension.Initializable;
import org.unidal.lookup.extension.InitializationException;
import org.unidal.lookup.logging.LogEnabled;
import org.unidal.lookup.logging.Logger;
import com.dianping.cat.Cat;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.configuration.client.entity.Server;
import com.dianping.cat.configuration.client.transform.DefaultSaxParser;
public class DefaultClientConfigManager implements LogEnabled, ClientConfigManager, Initializable {
	private static final String CAT_CLIENT_XML = "/META-INF/cat/client.xml";
	private static final String PROPERTIES_CLIENT_XML = "/META-INF/app.properties";
	
	private static final String XML = "/data/appdatas/cat/client.xml";
	private Logger m_logger;
	private ClientConfig m_config;
	@Override
	public void enableLogging(Logger logger) {
		m_logger = logger;
	}
	@Override
	public Domain getDomain() {
		Domain domain = null;
		if (m_config != null) {
			Map<String, Domain> domains = m_config.getDomains();
			domain = domains.isEmpty() ? null : domains.values().iterator().next();
		}
		if (domain != null) {
			return domain;
		} else {
			return new Domain("UNKNOWN").setEnabled(false);
		}
	}
	@Override
	public int getMaxMessageLength() {
		if (m_config == null) {
			return 5000;
		} else {
			return getDomain().getMaxMessageSize();
		}
	}
	@Override
	public String getServerConfigUrl() {
		if (m_config == null) {
			return null;
		} else {
			List<Server> servers = m_config.getServers();
			for (Server server : servers) {
				Integer httpPort = server.getHttpPort();
				if (httpPort == null || httpPort == 0) {
					httpPort = 8080;
				}
				return String.format("http://%s:%d/cat/s/router?domain=%s&ip=%s&op=json", server.getIp().trim(), httpPort,
				      getDomain().getId(), NetworkInterfaceManager.INSTANCE.getLocalHostAddress());
			}
		}
		return null;
	}
	@Override
	public List<Server> getServers() {
		if (m_config == null) {
			return Collections.emptyList();
		} else {
			return m_config.getServers();
		}
	}
	@Override
	public int getTaggedTransactionCacheSize() {
		return 1024;
	}
	@Override
	public boolean isCatEnabled() {
		if (m_config == null) {
			return false;
		} else {
			return m_config.isEnabled();
		}
	}
	@Override
	public boolean isDumpLocked() {
		if (m_config == null) {
			return false;
		} else {
			return m_config.isDumpLocked();
		}
	}
	private ClientConfig loadConfigFromEnviroment() {
		String appName = loadProjectName();
		if (appName != null) {
			ClientConfig config = new ClientConfig();
			config.addDomain(new Domain(appName));
			return config;
		}
		return null;
	}
	private ClientConfig loadConfigFromXml() {
		InputStream in = null;
		try {
			in = Thread.currentThread().getContextClassLoader().getResourceAsStream(CAT_CLIENT_XML);
			if (in == null) {
				in = Cat.class.getResourceAsStream(CAT_CLIENT_XML);
			}
			if (in != null) {
				String xml = Files.forIO().readFrom(in, "utf-8");
				m_logger.info(String.format("Resource file(%s) found.", Cat.class.getResource(CAT_CLIENT_XML)));
				return DefaultSaxParser.parse(xml);
			}
			return null;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
				}
			}
		}
		return null;
	}
	private String loadProjectName() {
		String appName = System.getProperty("app.name");
		if(appName != null) {
			m_logger.info(String.format("Find domain name %s from System.properties", appName));
		} else {
			appName = loadProjectNameFromPropertyFile();
		}
		return appName;
	}
	private String loadProjectNameFromPropertyFile() {
		String appName = null;
		InputStream in = null;
		try {
			in = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_CLIENT_XML);
			if (in == null) {
				in = Cat.class.getResourceAsStream(PROPERTIES_CLIENT_XML);
			}
			if (in != null) {
				Properties prop = new Properties();
				prop.load(in);
				appName = prop.getProperty("app.name");
				if (appName != null) {
					m_logger.info(String.format("Find domain name %s from app.properties.", appName));
				} else {
					m_logger.info(String.format("Can't find app.name from app.properties."));
					return null;
				}
			} else {
				m_logger.info(String.format("Can't find app.properties in %s", PROPERTIES_CLIENT_XML));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
				}
			}
		}
		return appName;
	}
	@Override
	public void initialize() throws InitializationException {
		File configFile = new File(XML);
		
		initialize(configFile);
	}
	@Override
   public void initialize(File configFile) throws InitializationException {
		try {
			ClientConfig globalConfig = null;
			ClientConfig clientConfig = null;
			if (configFile != null) {
				if (configFile.exists()) {
					String xml = Files.forIO().readFrom(configFile.getCanonicalFile(), "utf-8");
					globalConfig = DefaultSaxParser.parse(xml);
					m_logger.info(String.format("Global config file(%s) found.", configFile));
				} else {
					m_logger.warn(String.format("Global config file(%s) not found, IGNORED.", configFile));
				}
			}
			// load the client configure from Java class-path
			clientConfig = loadConfigFromEnviroment();
			if (clientConfig == null) {
				clientConfig = loadConfigFromXml();
			}
			// merge the two configures together to make it effected
			if (globalConfig != null && clientConfig != null) {
				globalConfig.accept(new ClientConfigMerger(clientConfig));
			}
			if (clientConfig != null) {
				clientConfig.accept(new ClientConfigValidator());
			}
			m_config = clientConfig;
		} catch (Exception e) {
			throw new InitializationException(e.getMessage(), e);
		}
   }
}

+ 26 - 0
cat-client/src/main/java/com/dianping/cat/configuration/KVConfig.java

@ -0,0 +1,26 @@
package com.dianping.cat.configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class KVConfig {
	private Map<String, String> m_kvs = new HashMap<String, String>();
	public Set<String> getKeys() {
		return m_kvs.keySet();
	}
	public Map<String, String> getKvs() {
		return m_kvs;
	}
	public String getValue(String key) {
		return m_kvs.get(key);
	}
	public void setKvs(Map<String, String> kvs) {
		m_kvs = kvs;
	}
}

+ 18 - 0
cat-client/src/main/java/com/dianping/cat/configuration/NetworkInterfaceManager.java

@ -0,0 +1,18 @@
package com.dianping.cat.configuration;
import org.unidal.helper.Inets;
public enum NetworkInterfaceManager {
	INSTANCE;
	private NetworkInterfaceManager() {
	}
	public String getLocalHostAddress() {
		return Inets.IP4.getLocalHostAddress();
	}
	public String getLocalHostName() {
		return Inets.IP4.getLocalHostName();
	}
}

+ 81 - 0
cat-client/src/main/java/com/dianping/cat/log4j/CatAppender.java

@ -0,0 +1,81 @@
package com.dianping.cat.log4j;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Trace;
public class CatAppender extends AppenderSkeleton {
	@Override
	protected void append(LoggingEvent event) {
		Level level = event.getLevel();
		if (level.isGreaterOrEqual(Level.ERROR)) {
			logError(event);
		} else if (Cat.getManager().isTraceMode()) {
			logTrace(event);
		}
	}
	private String buildExceptionStack(Throwable exception) {
		if (exception != null) {
			StringWriter writer = new StringWriter(2048);
			exception.printStackTrace(new PrintWriter(writer));
			return writer.toString();
		} else {
			return "";
		}
	}
	@Override
	public void close() {
	}
	private void logError(LoggingEvent event) {
		ThrowableInformation info = event.getThrowableInformation();
		if (info != null) {
			Throwable exception = info.getThrowable();
			Object message = event.getMessage();
			if (message != null) {
				Cat.logError(String.valueOf(message), exception);
			} else {
				Cat.logError(exception);
			}
		}
	}
	private void logTrace(LoggingEvent event) {
		String type = "Log4j";
		String name = event.getLevel().toString();
		Object message = event.getMessage();
		String data;
		if (message instanceof Throwable) {
			data = buildExceptionStack((Throwable) message);
		} else {
			data = event.getMessage().toString();
		}
		ThrowableInformation info = event.getThrowableInformation();
		if (info != null) {
			data = data + '\n' + buildExceptionStack(info.getThrowable());
		}
		Cat.logTrace(type, name, Trace.SUCCESS, data);
	}
	@Override
	public boolean requiresLayout() {
		return false;
	}
}

+ 74 - 0
cat-client/src/main/java/com/dianping/cat/log4j/Log4j2Appender.java

@ -0,0 +1,74 @@
package com.dianping.cat.log4j;
import java.io.Serializable;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AppenderLoggingException;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.message.Message;
import com.dianping.cat.Cat;
@Plugin(name = "CatAppender", category = "Core", elementType = "appender", printObject = true)
public class Log4j2Appender extends AbstractAppender {
	private static final long serialVersionUID = 2705802038361151598L;
	protected Log4j2Appender(String name, Filter filter, Layout<? extends Serializable> layout,
	      final boolean ignoreExceptions) {
		super(name, filter, layout, ignoreExceptions);
	}
	@Override
	public void append(LogEvent event) {
		try {
			Level level = event.getLevel();
			if (level.isMoreSpecificThan(Level.ERROR)) {
				logError(event);
			}
		} catch (Exception ex) {
			if (!ignoreExceptions()) {
				throw new AppenderLoggingException(ex);
			}
		}
	}
	@PluginFactory
	public static Log4j2Appender createAppender(@PluginAttribute("name") String name,
	      @PluginElement("Layout") Layout<? extends Serializable> layout, @PluginElement("Filter") final Filter filter,
	      @PluginAttribute("otherAttribute") String otherAttribute) {
		if (name == null) {
			return null;
		}
		if (layout == null) {
			layout = PatternLayout.createDefaultLayout();
		}
		return new Log4j2Appender(name, filter, layout, true);
	}
	private void logError(LogEvent event) {
		ThrowableProxy info = event.getThrownProxy();
		if (info != null) {
			Throwable exception = info.getThrowable();
			Message message = event.getMessage();
			if (message != null) {
				Cat.logError(message.getFormattedMessage(), exception);
			} else {
				Cat.logError(exception);
			}
		}
	}
}

+ 40 - 0
cat-client/src/main/java/com/dianping/cat/logback/CatLogbackAppender.java

@ -0,0 +1,40 @@
package com.dianping.cat.logback;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.LogbackException;
import com.dianping.cat.Cat;
public class CatLogbackAppender extends AppenderBase<ILoggingEvent> {
	@Override
	protected void append(ILoggingEvent event) {
		try {
			Level level = event.getLevel();
			if (level.isGreaterOrEqual(Level.ERROR)) {
				logError(event);
			}
		} catch (Exception ex) {
			throw new LogbackException(event.getFormattedMessage(), ex);
		}
	}
	private void logError(ILoggingEvent event) {
		ThrowableProxy info = (ThrowableProxy) event.getThrowableProxy();
		if (info != null) {
			Throwable exception = info.getThrowable();
			Object message = event.getFormattedMessage();
			if (message != null) {
				Cat.logError(String.valueOf(message), exception);
			} else {
				Cat.logError(exception);
			}
		}
	}
}

+ 24 - 0
cat-client/src/main/java/com/dianping/cat/message/Event.java

@ -0,0 +1,24 @@
package com.dianping.cat.message;
/**
 * <p>
 * <code>Event</code> is used to log anything interesting happens at a specific time. Such as an exception thrown, a
 * review added by user, a new user registered, an user logged into the system etc.
 * </p>
 * 
 * <p>
 * However, if it could be failure, or last for a long time, such as a remote API call, database call or search engine
 * call etc. It should be logged as a <code>Transaction</code>
 * </p>
 * 
 * <p>
 * All CAT message will be constructed as a message tree and send to back-end for further analysis, and for monitoring.
 * Only <code>Transaction</code> can be a tree node, all other message will be the tree leaf. The transaction without
 * other messages nested is an atomic transaction.
 * </p>
 * 
 * @author Frankie Wu
 */
public interface Event extends Message {
}

+ 26 - 0
cat-client/src/main/java/com/dianping/cat/message/Heartbeat.java

@ -0,0 +1,26 @@
package com.dianping.cat.message;
/**
 * <p>
 * <code>Heartbeat</code> is used to log data that happens in a regular intervals, for example once per second, such as
 * system load, CPU percentage, memory usage, thread pool statistics, cache hit/miss rate, service manifest etc., and
 * even some configuration could be carried by <code>Heartbeat</code>. There could be some good use cases, for example
 * health checker and load balancer, that make good use of it.
 * </p>
 * 
 * <p>
 * <code>Heartbeat</code> should never be used per request since the request is not regular predictable, instead it
 * could be logged in a daemon background thread, or something like a Timer.
 * </p>
 * 
 * <p>
 * All CAT message will be constructed as a message tree and send to back-end for further analysis, and for monitoring.
 * Only <code>Transaction</code> can be a tree node, all other message will be the tree leaf. The transaction without
 * other messages nested is an atomic transaction.
 * </p>
 * 
 * @author Frankie Wu
 */
public interface Heartbeat extends Message {
}

+ 112 - 0
cat-client/src/main/java/com/dianping/cat/message/Message.java

@ -0,0 +1,112 @@
package com.dianping.cat.message;
/**
 * <p>
 * Message represents data collected during application runtime. It will be sent to back-end system asynchronous for
 * further processing.
 * </p>
 * 
 * <p>
 * Super interface of <code>Event</code>, <code>Heartbeat</code> and <code>Transaction</code>.
 * </p>
 * 
 * @see Event, Heartbeat, Transaction
 * @author Frankie Wu
 */
public interface Message {
	public static final String SUCCESS = "0";
	/**
	 * add one or multiple key-value pairs to the message.
	 * 
	 * @param keyValuePairs
	 *           key-value pairs like 'a=1&b=2&...'
	 */
	public void addData(String keyValuePairs);
	/**
	 * add one key-value pair to the message.
	 * 
	 * @param key
	 * @param value
	 */
	public void addData(String key, Object value);
	/**
	 * Complete the message construction.
	 */
	public void complete();
	/**
	 * @return key value pairs data
	 */
	public Object getData();
	/**
	 * Message name.
	 * 
	 * @return message name
	 */
	public String getName();
	/**
	 * Get the message status.
	 * 
	 * @return message status. "0" means success, otherwise error code.
	 */
	public String getStatus();
	/**
	 * The time stamp the message was created.
	 * 
	 * @return message creation time stamp in milliseconds
	 */
	public long getTimestamp();
	/**
	 * Message type.
	 * 
	 * <p>
	 * Typical message types are:
	 * <ul>
	 * <li>URL: maps to one method of an action</li>
	 * <li>Service: maps to one method of service call</li>
	 * <li>Search: maps to one method of search call</li>
	 * <li>SQL: maps to one SQL statement</li>
	 * <li>Cache: maps to one cache access</li>
	 * <li>Error: maps to java.lang.Throwable (java.lang.Exception and java.lang.Error)</li>
	 * </ul>
	 * </p>
	 * 
	 * @return message type
	 */
	public String getType();
	/**
	 * If the complete() method was called or not.
	 * 
	 * @return true means the complete() method was called, false otherwise.
	 */
	public boolean isCompleted();
	/**
	 * @return
	 */
	public boolean isSuccess();
	/**
	 * Set the message status.
	 * 
	 * @param status
	 *           message status. "0" means success, otherwise error code.
	 */
	public void setStatus(String status);
	/**
	 * Set the message status with exception class name.
	 * 
	 * @param e
	 *           exception.
	 */
	public void setStatus(Throwable e);
}

+ 274 - 0
cat-client/src/main/java/com/dianping/cat/message/MessageProducer.java

@ -0,0 +1,274 @@
package com.dianping.cat.message;
/**
 * <p>
 * Message factory is used to create new transaction,event and/or heartbeat.
 * </p>
 * 
 * <p>
 * Normally, application code logs message in following ways, for example:
 * <ul>
 * <li>Event
 * 
 * <pre>
 * public class MyClass { 
 *    public static MessageFactory CAT = Cat.getFactory();
 * 
 *    public void bizMethod() { 
 *       Event event = CAT.newEvent("Review", "New");
 * 
 *       event.addData("id", 12345); 
 *       event.addData("user", "john");
 *       ...
 *       event.setStatus("0"); 
 *       event.complete(); 
 *    }
 *    ...
 * }
 * </pre>
 * 
 * </li>
 * <li>Heartbeat
 * 
 * <pre>
 * public class MyClass { 
 *    public static MessageFactory CAT = Cat.getFactory();
 * 
 *    public void bizMethod() { 
 *       Heartbeat event = CAT.newHeartbeat("System", "Status");
 * 
 *       event.addData("ip", "192.168.10.111");
 *       event.addData("host", "host-1");
 *       event.addData("load", "2.1");
 *       event.addData("cpu", "0.12,0.10");
 *       event.addData("memory.total", "2G");
 *       event.addData("memory.free", "456M");
 *       event.setStatus("0");
 *       event.complete();
 *    }
 *    ...
 * }
 * </pre>
 * 
 * </li>
 * <li>Transaction
 * 
 * <pre>
 * public class MyClass { 
 *    public static MessageFactory CAT = Cat.getFactory();
 * 
 *    public void bizMethod() { 
 *       Transaction t = CAT.newTransaction("URL", "MyPage");
 * 
 *       try {
 *          // do your business here
 *          t.addData("k1", "v1");
 *          t.addData("k2", "v2");
 *          t.addData("k3", "v3");
 *          Thread.sleep(30);
 * 
 *          t.setStatus("0");
 *       } catch (Exception e) {
 *          t.setStatus(e);
 *       } finally {
 *          t.complete();
 *       }
 *    }
 *    ...
 * }
 * </pre>
 * 
 * </li>
 * </ul>
 * 
 * or logs event or heartbeat in one shot, for example:
 * <ul>
 * <li>Event
 * 
 * <pre>
 * public class MyClass { 
 *    public static MessageFactory CAT = Cat.getFactory();
 * 
 *    public void bizMethod() { 
 *       CAT.logEvent("Review", "New", "0", "id=12345&user=john");
 *    }
 *    ...
 * }
 * </pre>
 * 
 * </li>
 * <li>Heartbeat
 * 
 * <pre>
 * public class MyClass { 
 *    public static MessageFactory CAT = Cat.getFactory();
 * 
 *    public void bizMethod() { 
 *       CAT.logHeartbeat("System", "Status", "0", "ip=192.168.10.111&host=host-1&load=2.1&cpu=0.12,0.10&memory.total=2G&memory.free=456M");
 *    }
 *    ...
 * }
 * </pre>
 * 
 * </li>
 * </ul>
 * </p>
 * 
 * @author Frankie Wu
 */
public interface MessageProducer {
	/**
	 * Create a new message id.
	 * 
	 * @return new message id
	 */
	public String createMessageId();
	/**
	 * Check if the CAT client is enabled for current domain.
	 * 
	 * @return true if CAT client is enabled, false means CAT client is disabled.
	 */
	public boolean isEnabled();
	/**
	 * Log an error.
	 * 
	 * @param cause
	 *           root cause exception
	 */
	public void logError(Throwable cause);
	/**
	 * Log an error.
	 * 
	 * @param cause
	 *           root cause exception
	 */
	public void logError(String message, Throwable cause);
	/**
	 * Log an event in one shot with SUCCESS status.
	 * 
	 * @param type
	 *           event type
	 * @param name
	 *           event name
	 */
	public void logEvent(String type, String name);
	/**
	 * Log an trace in one shot with SUCCESS status.
	 * 
	 * @param type
	 *           trace type
	 * @param name
	 *           trace name
	 */
	public void logTrace(String type, String name);
	/**
	 * Log an event in one shot.
	 * 
	 * @param type
	 *           event type
	 * @param name
	 *           event name
	 * @param status
	 *           "0" means success, otherwise means error code
	 * @param nameValuePairs
	 *           name value pairs in the format of "a=1&b=2&..."
	 */
	public void logEvent(String type, String name, String status, String nameValuePairs);
	/**
	 * Log an trace in one shot.
	 * 
	 * @param type
	 *           trace type
	 * @param name
	 *           trace name
	 * @param status
	 *           "0" means success, otherwise means error code
	 * @param nameValuePairs
	 *           name value pairs in the format of "a=1&b=2&..."
	 */
	public void logTrace(String type, String name, String status, String nameValuePairs);
	/**
	 * Log a heartbeat in one shot.
	 * 
	 * @param type
	 *           heartbeat type
	 * @param name
	 *           heartbeat name
	 * @param status
	 *           "0" means success, otherwise means error code
	 * @param nameValuePairs
	 *           name value pairs in the format of "a=1&b=2&..."
	 */
	public void logHeartbeat(String type, String name, String status, String nameValuePairs);
	/**
	 * Log a metric in one shot.
	 * 
	 * @param name
	 *           metric name
	 * @param status
	 *           "0" means success, otherwise means error code
	 * @param nameValuePairs
	 *           name value pairs in the format of "a=1&b=2&..."
	 */
	public void logMetric(String name, String status, String nameValuePairs);
	/**
	 * Create a new event with given type and name.
	 * 
	 * @param type
	 *           event type
	 * @param name
	 *           event name
	 */
	public Event newEvent(String type, String name);
	/**
	 * Create a new trace with given type and name.
	 * 
	 * @param type
	 *           trace type
	 * @param name
	 *           trace name
	 */
	public Trace newTrace(String type, String name);
	/**
	 * Create a new heartbeat with given type and name.
	 * 
	 * @param type
	 *           heartbeat type
	 * @param name
	 *           heartbeat name
	 */
	public Heartbeat newHeartbeat(String type, String name);
	/**
	 * Create a new metric with given type and name.
	 * 
	 * @param type
	 *           metric type
	 * @param name
	 *           metric name
	 */
	public Metric newMetric(String type, String name);
	/**
	 * Create a new transaction with given type and name.
	 * 
	 * @param type
	 *           transaction type
	 * @param name
	 *           transaction name
	 */
	public Transaction newTransaction(String type, String name);
}

+ 24 - 0
cat-client/src/main/java/com/dianping/cat/message/Metric.java

@ -0,0 +1,24 @@
package com.dianping.cat.message;
/**
 * <p>
 * <code>Metric</code> is used to log business data point happens at a specific time. Such as an exception thrown, a
 * review added by user, a new user registered, an user logged into the system etc.
 * </p>
 * 
 * <p>
 * However, if it could be failure, or last for a long time, such as a remote API call, database call or search engine
 * call etc. It should be logged as a <code>Transaction</code>
 * </p>
 * 
 * <p>
 * All CAT message will be constructed as a message tree and send to back-end for further analysis, and for monitoring.
 * Only <code>Transaction</code> can be a tree node, all other message will be the tree leaf. The transaction without
 * other messages nested is an atomic transaction.
 * </p>
 * 
 * @author Frankie Wu
 */
public interface Metric extends Message {
}

+ 18 - 0
cat-client/src/main/java/com/dianping/cat/message/Trace.java

@ -0,0 +1,18 @@
package com.dianping.cat.message;
/**
 * <p>
 * <code>Trace</code> is used to log anything for trace message info happens at a specific time. Such as an debug or info message.
 * </p>
 * 
 * <p>
 * All CAT message will be constructed as a message tree and send to back-end for further analysis, and for monitoring.
 * Only <code>Transaction</code> can be a tree node, all other message will be the tree leaf. The transaction without
 * other messages nested is an atomic transaction.
 * </p>
 * 
 * @author Frankie Wu
 */
public interface Trace extends Message {
}

+ 81 - 0
cat-client/src/main/java/com/dianping/cat/message/Transaction.java

@ -0,0 +1,81 @@
package com.dianping.cat.message;
import java.util.List;
/**
 * <p>
 * <code>Transaction</code> is any interesting unit of work that takes time to complete and may fail.
 * </p>
 * 
 * <p>
 * Basically, all data access across the boundary needs to be logged as a <code>Transaction</code> since it may fail and
 * time consuming. For example, URL request, disk IO, JDBC query, search query, HTTP request, 3rd party API call etc.
 * </p>
 * 
 * <p>
 * Sometime if A needs call B which is owned by another team, although A and B are deployed together without any
 * physical boundary. To make the ownership clear, there could be some <code>Transaction</code> logged when A calls B.
 * </p>
 * 
 * <p>
 * Most of <code>Transaction</code> should be logged in the infrastructure level or framework level, which is
 * transparent to the application.
 * </p>
 * 
 * <p>
 * All CAT message will be constructed as a message tree and send to back-end for further analysis, and for monitoring.
 * Only <code>Transaction</code> can be a tree node, all other message will be the tree leaf. The transaction without
 * other messages nested is an atomic transaction.
 * </p>
 * 
 * @author Frankie Wu
 */
public interface Transaction extends Message {
	/**
	 * Add one nested child message to current transaction.
	 * 
	 * @param message
	 *           to be added
	 */
	public Transaction addChild(Message message);
	/**
	 * Get all children message within current transaction.
	 * 
	 * <p>
	 * Typically, a <code>Transaction</code> can nest other <code>Transaction</code>s, <code>Event</code>s and
	 * <code>Heartbeat</code> s, while an <code>Event</code> or <code>Heartbeat</code> can't nest other messages.
	 * </p>
	 * 
	 * @return all children messages, empty if there is no nested children.
	 */
	public List<Message> getChildren();
	/**
	 * How long the transaction took from construction to complete. Time unit is microsecond.
	 * 
	 * @return duration time in microsecond
	 */
	public long getDurationInMicros();
	/**
	 * How long the transaction took from construction to complete. Time unit is millisecond.
	 * 
	 * @return duration time in millisecond
	 */
	public long getDurationInMillis();
	/**
	 * Has children or not. An atomic transaction does not have any children message.
	 * 
	 * @return true if child exists, else false.
	 */
	public boolean hasChildren();
	/**
	 * Check if the transaction is stand-alone or belongs to another one.
	 * 
	 * @return true if it's an root transaction.
	 */
	public boolean isStandalone();
}

+ 137 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/AbstractMessage.java

@ -0,0 +1,137 @@
package com.dianping.cat.message.internal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.nio.charset.Charset;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.spi.codec.PlainTextMessageCodec;
public abstract class AbstractMessage implements Message {
	private String m_type;
	private String m_name;
	private String m_status = "unset";
	private long m_timestampInMillis;
	private CharSequence m_data;
	private boolean m_completed;
	public AbstractMessage(String type, String name) {
		m_type = String.valueOf(type);
		m_name = String.valueOf(name);
		m_timestampInMillis = MilliSecondTimer.currentTimeMillis();
	}
	@Override
	public void addData(String keyValuePairs) {
		if (m_data == null) {
			m_data = keyValuePairs;
		} else if (m_data instanceof StringBuilder) {
			((StringBuilder) m_data).append('&').append(keyValuePairs);
		} else {
			StringBuilder sb = new StringBuilder(m_data.length() + keyValuePairs.length() + 16);
			sb.append(m_data).append('&');
			sb.append(keyValuePairs);
			m_data = sb;
		}
	}
	@Override
	public void addData(String key, Object value) {
		if (m_data instanceof StringBuilder) {
			((StringBuilder) m_data).append('&').append(key).append('=').append(value);
		} else {
			String str = String.valueOf(value);
			int old = m_data == null ? 0 : m_data.length();
			StringBuilder sb = new StringBuilder(old + key.length() + str.length() + 16);
			if (m_data != null) {
				sb.append(m_data).append('&');
			}
			sb.append(key).append('=').append(str);
			m_data = sb;
		}
	}
	@Override
	public CharSequence getData() {
		if (m_data == null) {
			return "";
		} else {
			return m_data;
		}
	}
	@Override
	public String getName() {
		return m_name;
	}
	@Override
	public String getStatus() {
		return m_status;
	}
	@Override
	public long getTimestamp() {
		return m_timestampInMillis;
	}
	@Override
	public String getType() {
		return m_type;
	}
	@Override
	public boolean isCompleted() {
		return m_completed;
	}
	@Override
	public boolean isSuccess() {
		return Message.SUCCESS.equals(m_status);
	}
	public void setCompleted(boolean completed) {
		m_completed = completed;
	}
	public void setName(String name) {
		m_name = name;
	}
	@Override
	public void setStatus(String status) {
		m_status = status;
	}
	@Override
	public void setStatus(Throwable e) {
		m_status = e.getClass().getName();
	}
	public void setTimestamp(long timestamp) {
		m_timestampInMillis = timestamp;
	}
	public void setType(String type) {
		m_type = type;
	}
	@Override
	public String toString() {
		PlainTextMessageCodec codec = new PlainTextMessageCodec();
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
		codec.encodeMessage(this, buf);
		codec.reset();
		return buf.toString(Charset.forName("utf-8"));
	}
}

+ 27 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/DefaultEvent.java

@ -0,0 +1,27 @@
package com.dianping.cat.message.internal;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.spi.MessageManager;
public class DefaultEvent extends AbstractMessage implements Event {
	private MessageManager m_manager;
	public DefaultEvent(String type, String name) {
		super(type, name);
	}
	public DefaultEvent(String type, String name, MessageManager manager) {
		super(type, name);
		m_manager = manager;
	}
	@Override
	public void complete() {
		setCompleted(true);
		if (m_manager != null) {
			m_manager.add(this);
		}
	}
}

+ 27 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/DefaultHeartbeat.java

@ -0,0 +1,27 @@
package com.dianping.cat.message.internal;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.spi.MessageManager;
public class DefaultHeartbeat extends AbstractMessage implements Heartbeat {
	private MessageManager m_manager;
	public DefaultHeartbeat(String type, String name) {
		super(type, name);
	}
	public DefaultHeartbeat(String type, String name, MessageManager manager) {
		super(type, name);
		m_manager = manager;
   }
	@Override
	public void complete() {
		setCompleted(true);
		if (m_manager != null) {
			m_manager.add(this);
		}
	}
}

+ 531 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/DefaultMessageManager.java

@ -0,0 +1,531 @@
package com.dianping.cat.message.internal;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.unidal.lookup.ContainerHolder;
import org.unidal.lookup.annotation.Inject;
import org.unidal.lookup.extension.Initializable;
import org.unidal.lookup.extension.InitializationException;
import org.unidal.lookup.logging.LogEnabled;
import org.unidal.lookup.logging.Logger;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.configuration.NetworkInterfaceManager;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.io.MessageSender;
import com.dianping.cat.message.io.TransportManager;
import com.dianping.cat.message.spi.MessageManager;
import com.dianping.cat.message.spi.MessageTree;
import com.dianping.cat.message.spi.internal.DefaultMessageTree;
public class DefaultMessageManager extends ContainerHolder implements MessageManager, Initializable, LogEnabled {
	@Inject
	private ClientConfigManager m_configManager;
	@Inject
	private TransportManager m_transportManager;
	@Inject
	private MessageIdFactory m_factory;
	// we don't use static modifier since MessageManager is configured as singleton
	private ThreadLocal<Context> m_context = new ThreadLocal<Context>();
	private long m_throttleTimes;
	private Domain m_domain;
	private String m_hostName;
	private boolean m_firstMessage = true;
	private TransactionHelper m_validator = new TransactionHelper();
	private Logger m_logger;
	@Override
	public void add(Message message) {
		Context ctx = getContext();
		if (ctx != null) {
			ctx.add(message);
		}
	}
	@Override
	public void enableLogging(Logger logger) {
		m_logger = logger;
	}
	@Override
	public void end(Transaction transaction) {
		Context ctx = getContext();
		if (ctx != null && transaction.isStandalone()) {
			if (ctx.end(this, transaction)) {
				m_context.remove();
			}
		}
	}
	public void flush(MessageTree tree) {
		if (tree.getMessageId() == null) {
			tree.setMessageId(nextMessageId());
		}
		MessageSender sender = m_transportManager.getSender();
		if (sender != null && isMessageEnabled()) {
			sender.send(tree);
			reset();
		} else {
			m_throttleTimes++;
			if (m_throttleTimes % 10000 == 0 || m_throttleTimes == 1) {
				m_logger.info("Cat Message is throttled! Times:" + m_throttleTimes);
			}
		}
	}
	public ClientConfigManager getConfigManager() {
		return m_configManager;
	}
	private Context getContext() {
		Context ctx = m_context.get();
		if (ctx != null) {
			return ctx;
		} else {
			if (m_domain != null) {
				ctx = new Context(m_domain.getId(), m_hostName, m_domain.getIp());
			} else {
				ctx = new Context("Unknown", m_hostName, "");
			}
			m_context.set(ctx);
			return ctx;
		}
	}
	@Override
	public String getDomain() {
		return m_domain.getId();
	}
	public String getMetricType() {
		return "";
	}
	@Override
	public Transaction getPeekTransaction() {
		Context ctx = getContext();
		if (ctx != null) {
			return ctx.peekTransaction(this);
		} else {
			return null;
		}
	}
	@Override
	public MessageTree getThreadLocalMessageTree() {
		Context ctx = m_context.get();
		if (ctx == null) {
			setup();
		}
		ctx = m_context.get();
		return ctx.m_tree;
	}
	@Override
	public boolean hasContext() {
		return m_context.get() != null;
	}
	@Override
	public void initialize() throws InitializationException {
		m_domain = m_configManager.getDomain();
		m_hostName = NetworkInterfaceManager.INSTANCE.getLocalHostName();
		if (m_domain.getIp() == null) {
			m_domain.setIp(NetworkInterfaceManager.INSTANCE.getLocalHostAddress());
		}
		// initialize domain and IP address
		try {
			m_factory.initialize(m_domain.getId());
		} catch (IOException e) {
			throw new InitializationException("Error while initializing MessageIdFactory!", e);
		}
	}
	@Override
	public boolean isCatEnabled() {
		return m_domain != null && m_domain.isEnabled() && m_configManager.isCatEnabled();
	}
	@Override
	public boolean isMessageEnabled() {
		return m_domain != null && m_domain.isEnabled() && m_context.get() != null && m_configManager.isCatEnabled();
	}
	public boolean isTraceMode() {
		Context content = getContext();
		if (content != null) {
			return content.isTraceMode();
		} else {
			return false;
		}
	}
	public String nextMessageId() {
		return m_factory.getNextId();
	}
	@Override
	public void reset() {
		// destroy current thread local data
		Context ctx = m_context.get();
		if (ctx != null) {
			if (ctx.m_totalDurationInMicros == 0) {
				ctx.m_stack.clear();
				ctx.m_knownExceptions.clear();
				m_context.remove();
			} else {
				ctx.m_knownExceptions.clear();
			}
		}
	}
	public void setMetricType(String metricType) {
	}
	public void setTraceMode(boolean traceMode) {
		Context context = getContext();
		if (context != null) {
			context.setTraceMode(traceMode);
		}
	}
	@Override
	public void setup() {
		Context ctx;
		if (m_domain != null) {
			ctx = new Context(m_domain.getId(), m_hostName, m_domain.getIp());
		} else {
			ctx = new Context("Unknown", m_hostName, "");
		}
		m_context.set(ctx);
	}
	boolean shouldLog(Throwable e) {
		Context ctx = m_context.get();
		if (ctx != null) {
			return ctx.shouldLog(e);
		} else {
			return true;
		}
	}
	@Override
	public void start(Transaction transaction, boolean forked) {
		Context ctx = getContext();
		if (ctx != null) {
			ctx.start(transaction, forked);
		} else if (m_firstMessage) {
			m_firstMessage = false;
			m_logger.warn("CAT client is not enabled because it's not initialized yet");
		}
	}
	class Context {
		private MessageTree m_tree;
		private Stack<Transaction> m_stack;
		private int m_length;
		private boolean m_traceMode;
		private long m_totalDurationInMicros; // for truncate message
		private Set<Throwable> m_knownExceptions;
		public Context(String domain, String hostName, String ipAddress) {
			m_tree = new DefaultMessageTree();
			m_stack = new Stack<Transaction>();
			Thread thread = Thread.currentThread();
			String groupName = thread.getThreadGroup().getName();
			m_tree.setThreadGroupName(groupName);
			m_tree.setThreadId(String.valueOf(thread.getId()));
			m_tree.setThreadName(thread.getName());
			m_tree.setDomain(domain);
			m_tree.setHostName(hostName);
			m_tree.setIpAddress(ipAddress);
			m_length = 1;
			m_knownExceptions = new HashSet<Throwable>();
		}
		public void add(Message message) {
			if (m_stack.isEmpty()) {
				MessageTree tree = m_tree.copy();
				tree.setMessage(message);
				flush(tree);
			} else {
				Transaction parent = m_stack.peek();
				addTransactionChild(message, parent);
			}
		}
		private void addTransactionChild(Message message, Transaction transaction) {
			long treePeriod = trimToHour(m_tree.getMessage().getTimestamp());
			long messagePeriod = trimToHour(message.getTimestamp() - 10 * 1000L); // 10 seconds extra time allowed
			if (treePeriod < messagePeriod || m_length >= m_configManager.getMaxMessageLength()) {
				m_validator.truncateAndFlush(this, message.getTimestamp());
			}
			transaction.addChild(message);
			m_length++;
		}
		private void adjustForTruncatedTransaction(Transaction root) {
			DefaultEvent next = new DefaultEvent("TruncatedTransaction", "TotalDuration");
			long actualDurationInMicros = m_totalDurationInMicros + root.getDurationInMicros();
			next.addData(String.valueOf(actualDurationInMicros));
			next.setStatus(Message.SUCCESS);
			root.addChild(next);
			m_totalDurationInMicros = 0;
		}
		/**
		 * return true means the transaction has been flushed.
		 * 
		 * @param manager
		 * @param transaction
		 * @return true if message is flushed, false otherwise
		 */
		public boolean end(DefaultMessageManager manager, Transaction transaction) {
			if (!m_stack.isEmpty()) {
				Transaction current = m_stack.pop();
				if (transaction == current) {
					m_validator.validate(m_stack.isEmpty() ? null : m_stack.peek(), current);
				} else {
					while (transaction != current && !m_stack.empty()) {
						m_validator.validate(m_stack.peek(), current);
						current = m_stack.pop();
					}
				}
				if (m_stack.isEmpty()) {
					MessageTree tree = m_tree.copy();
					m_tree.setMessageId(null);
					m_tree.setMessage(null);
					if (m_totalDurationInMicros > 0) {
						adjustForTruncatedTransaction((Transaction) tree.getMessage());
					}
					manager.flush(tree);
					return true;
				}
			}
			return false;
		}
		public boolean isTraceMode() {
			return m_traceMode;
		}
		public Transaction peekTransaction(DefaultMessageManager defaultMessageManager) {
			if (m_stack.isEmpty()) {
				return null;
			} else {
				return m_stack.peek();
			}
		}
		public void setTraceMode(boolean traceMode) {
			m_traceMode = traceMode;
		}
		public boolean shouldLog(Throwable e) {
			if (m_knownExceptions == null) {
				m_knownExceptions = new HashSet<Throwable>();
			}
			if (m_knownExceptions.contains(e)) {
				return false;
			} else {
				m_knownExceptions.add(e);
				return true;
			}
		}
		public void start(Transaction transaction, boolean forked) {
			if (!m_stack.isEmpty()) {
				// Do NOT make strong reference from parent transaction to forked transaction.
				// Instead, we create a "soft" reference to forked transaction later, via linkAsRunAway()
				// By doing so, there is no need for synchronization between parent and child threads.
				// Both threads can complete() anytime despite the other thread.
				Transaction parent = m_stack.peek();
				addTransactionChild(transaction, parent);
			} else {
				m_tree.setMessage(transaction);
			}
			if (!forked) {
				m_stack.push(transaction);
			}
		}
		private long trimToHour(long timestamp) {
			return timestamp - timestamp % (3600 * 1000L);
		}
	}
	class TransactionHelper {
		private void markAsNotCompleted(DefaultTransaction transaction) {
			DefaultEvent event = new DefaultEvent("cat", "BadInstrument");
			event.setStatus("TransactionNotCompleted");
			event.setCompleted(true);
			transaction.addChild(event);
			transaction.setCompleted(true);
		}
		private void migrateMessage(Stack<Transaction> stack, Transaction source, Transaction target, int level) {
			Transaction current = level < stack.size() ? stack.get(level) : null;
			boolean shouldKeep = false;
			for (Message child : source.getChildren()) {
				if (child != current) {
					target.addChild(child);
				} else {
					DefaultTransaction cloned = new DefaultTransaction(current.getType(), current.getName(),
					      DefaultMessageManager.this);
					cloned.setTimestamp(current.getTimestamp());
					cloned.setDurationInMicros(current.getDurationInMicros());
					cloned.addData(current.getData().toString());
					cloned.setStatus(Message.SUCCESS);
					target.addChild(cloned);
					migrateMessage(stack, current, cloned, level + 1);
					shouldKeep = true;
				}
			}
			source.getChildren().clear();
			if (shouldKeep) { // add it back
				source.addChild(current);
			}
		}
		public void truncateAndFlush(Context ctx, long timestamp) {
			MessageTree tree = ctx.m_tree;
			Stack<Transaction> stack = ctx.m_stack;
			Message message = tree.getMessage();
			if (message instanceof DefaultTransaction) {
				String id = tree.getMessageId();
				if (id == null) {
					id = nextMessageId();
					tree.setMessageId(id);
				}
				String rootId = tree.getRootMessageId();
				String childId = nextMessageId();
				DefaultTransaction source = (DefaultTransaction) message;
				DefaultTransaction target = new DefaultTransaction(source.getType(), source.getName(),
				      DefaultMessageManager.this);
				target.setTimestamp(source.getTimestamp());
				target.setDurationInMicros(source.getDurationInMicros());
				target.addData(source.getData().toString());
				target.setStatus(Message.SUCCESS);
				migrateMessage(stack, source, target, 1);
				for (int i = stack.size() - 1; i >= 0; i--) {
					DefaultTransaction t = (DefaultTransaction) stack.get(i);
					t.setTimestamp(timestamp);
					t.setDurationStart(System.nanoTime());
				}
				DefaultEvent next = new DefaultEvent("RemoteCall", "Next");
				next.addData(childId);
				next.setStatus(Message.SUCCESS);
				target.addChild(next);
				// tree is the parent, and m_tree is the child.
				MessageTree t = tree.copy();
				t.setMessage(target);
				ctx.m_tree.setMessageId(childId);
				ctx.m_tree.setParentMessageId(id);
				ctx.m_tree.setRootMessageId(rootId != null ? rootId : id);
				ctx.m_length = stack.size();
				ctx.m_totalDurationInMicros = ctx.m_totalDurationInMicros + target.getDurationInMicros();
				flush(t);
			}
		}
		public void validate(Transaction parent, Transaction transaction) {
			if (transaction.isStandalone()) {
				List<Message> children = transaction.getChildren();
				int len = children.size();
				for (int i = 0; i < len; i++) {
					Message message = children.get(i);
					if (message instanceof Transaction) {
						validate(transaction, (Transaction) message);
					}
				}
				if (!transaction.isCompleted() && transaction instanceof DefaultTransaction) {
					// missing transaction end, log a BadInstrument event so that
					// developer can fix the code
					markAsNotCompleted((DefaultTransaction) transaction);
				}
			} 
		}
	}
}

+ 246 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/DefaultMessageProducer.java

@ -0,0 +1,246 @@
package com.dianping.cat.message.internal;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.unidal.lookup.annotation.Inject;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.MessageProducer;
import com.dianping.cat.message.Metric;
import com.dianping.cat.message.Trace;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.spi.MessageManager;
public class DefaultMessageProducer implements MessageProducer {
	@Inject
	private MessageManager m_manager;
	@Inject
	private MessageIdFactory m_factory;
	@Override
	public String createMessageId() {
		return m_factory.getNextId();
	}
	@Override
	public boolean isEnabled() {
		return m_manager.isMessageEnabled();
	}
	@Override
	public void logError(String message, Throwable cause) {
		if (Cat.getManager().isCatEnabled()) {
			if (shouldLog(cause)) {
				m_manager.getThreadLocalMessageTree().setSample(false);
				StringWriter writer = new StringWriter(2048);
				if (message != null) {
					writer.write(message);
					writer.write(' ');
				}
				cause.printStackTrace(new PrintWriter(writer));
				String detailMessage = writer.toString();
				if (cause instanceof Error) {
					logEvent("Error", cause.getClass().getName(), "ERROR", detailMessage);
				} else if (cause instanceof RuntimeException) {
					logEvent("RuntimeException", cause.getClass().getName(), "ERROR", detailMessage);
				} else {
					logEvent("Exception", cause.getClass().getName(), "ERROR", detailMessage);
				}
			}
		} else {
			cause.printStackTrace();
		}
	}
	@Override
	public void logError(Throwable cause) {
		logError(null, cause);
	}
	@Override
	public void logEvent(String type, String name) {
		logEvent(type, name, Message.SUCCESS, null);
	}
	@Override
	public void logEvent(String type, String name, String status, String nameValuePairs) {
		Event event = newEvent(type, name);
		if (nameValuePairs != null && nameValuePairs.length() > 0) {
			event.addData(nameValuePairs);
		}
		event.setStatus(status);
		event.complete();
	}
	@Override
	public void logHeartbeat(String type, String name, String status, String nameValuePairs) {
		Heartbeat heartbeat = newHeartbeat(type, name);
		heartbeat.addData(nameValuePairs);
		heartbeat.setStatus(status);
		heartbeat.complete();
	}
	@Override
	public void logMetric(String name, String status, String nameValuePairs) {
		String type = "";
		Metric metric = newMetric(type, name);
		if (nameValuePairs != null && nameValuePairs.length() > 0) {
			metric.addData(nameValuePairs);
		}
		metric.setStatus(status);
		metric.complete();
	}
	@Override
	public void logTrace(String type, String name) {
		logTrace(type, name, Message.SUCCESS, null);
	}
	@Override
	public void logTrace(String type, String name, String status, String nameValuePairs) {
		if (m_manager.isTraceMode()) {
			Trace trace = newTrace(type, name);
			if (nameValuePairs != null && nameValuePairs.length() > 0) {
				trace.addData(nameValuePairs);
			}
			trace.setStatus(status);
			trace.complete();
		}
	}
	@Override
	public Event newEvent(String type, String name) {
		if (!m_manager.hasContext()) {
			m_manager.setup();
		}
		if (m_manager.isMessageEnabled()) {
			DefaultEvent event = new DefaultEvent(type, name, m_manager);
			return event;
		} else {
			return NullMessage.EVENT;
		}
	}
	public Event newEvent(Transaction parent, String type, String name) {
		if (!m_manager.hasContext()) {
			m_manager.setup();
		}
		if (m_manager.isMessageEnabled() && parent != null) {
			DefaultEvent event = new DefaultEvent(type, name);
			parent.addChild(event);
			return event;
		} else {
			return NullMessage.EVENT;
		}
	}
	@Override
	public Heartbeat newHeartbeat(String type, String name) {
		if (!m_manager.hasContext()) {
			m_manager.setup();
		}
		if (m_manager.isMessageEnabled()) {
			DefaultHeartbeat heartbeat = new DefaultHeartbeat(type, name, m_manager);
			m_manager.getThreadLocalMessageTree().setSample(false);
			return heartbeat;
		} else {
			return NullMessage.HEARTBEAT;
		}
	}
	@Override
	public Metric newMetric(String type, String name) {
		if (!m_manager.hasContext()) {
			m_manager.setup();
		}
		if (m_manager.isMessageEnabled()) {
			DefaultMetric metric = new DefaultMetric(type == null ? "" : type, name, m_manager);
			m_manager.getThreadLocalMessageTree().setSample(false);
			return metric;
		} else {
			return NullMessage.METRIC;
		}
	}
	@Override
	public Trace newTrace(String type, String name) {
		if (!m_manager.hasContext()) {
			m_manager.setup();
		}
		if (m_manager.isMessageEnabled()) {
			DefaultTrace trace = new DefaultTrace(type, name, m_manager);
			return trace;
		} else {
			return NullMessage.TRACE;
		}
	}
	@Override
	public Transaction newTransaction(String type, String name) {
		// this enable CAT client logging cat message without explicit setup
		if (!m_manager.hasContext()) {
			m_manager.setup();
		}
		if (m_manager.isMessageEnabled()) {
			DefaultTransaction transaction = new DefaultTransaction(type, name, m_manager);
			m_manager.start(transaction, false);
			return transaction;
		} else {
			return NullMessage.TRANSACTION;
		}
	}
	public Transaction newTransaction(Transaction parent, String type, String name) {
		// this enable CAT client logging cat message without explicit setup
		if (!m_manager.hasContext()) {
			m_manager.setup();
		}
		if (m_manager.isMessageEnabled() && parent != null) {
			DefaultTransaction transaction = new DefaultTransaction(type, name, m_manager);
			parent.addChild(transaction);
			transaction.setStandalone(false);
			return transaction;
		} else {
			return NullMessage.TRANSACTION;
		}
	}
	private boolean shouldLog(Throwable e) {
		if (m_manager instanceof DefaultMessageManager) {
			return ((DefaultMessageManager) m_manager).shouldLog(e);
		} else {
			return true;
		}
	}
}

+ 27 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/DefaultMetric.java

@ -0,0 +1,27 @@
package com.dianping.cat.message.internal;
import com.dianping.cat.message.Metric;
import com.dianping.cat.message.spi.MessageManager;
public class DefaultMetric extends AbstractMessage implements Metric {
	private MessageManager m_manager;
	public DefaultMetric(String type, String name) {
		super(type, name);
	}
	public DefaultMetric(String type, String name, MessageManager manager) {
		super(type, name);
		m_manager = manager;
	}
	@Override
	public void complete() {
		setCompleted(true);
		if (m_manager != null) {
			m_manager.add(this);
		}
	}
}

+ 27 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/DefaultTrace.java

@ -0,0 +1,27 @@
package com.dianping.cat.message.internal;
import com.dianping.cat.message.Trace;
import com.dianping.cat.message.spi.MessageManager;
public class DefaultTrace extends AbstractMessage implements Trace {
	private MessageManager m_manager;
	public DefaultTrace(String type, String name) {
		super(type, name);
	}
	public DefaultTrace(String type, String name, MessageManager manager) {
		super(type, name);
		m_manager = manager;
	}
	@Override
	public void complete() {
		setCompleted(true);
		if (m_manager != null) {
			m_manager.add(this);
		}
	}
}

+ 137 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/DefaultTransaction.java

@ -0,0 +1,137 @@
package com.dianping.cat.message.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.spi.MessageManager;
public class DefaultTransaction extends AbstractMessage implements Transaction {
	private long m_durationInMicro = -1; // must be less than 0
	private List<Message> m_children;
	private MessageManager m_manager;
	private boolean m_standalone;
	private long m_durationStart;
	public DefaultTransaction(String type, String name, MessageManager manager) {
		super(type, name);
		m_manager = manager;
		m_standalone = true;
		m_durationStart = System.nanoTime();
	}
	@Override
	public DefaultTransaction addChild(Message message) {
		if (m_children == null) {
			m_children = new ArrayList<Message>();
		}
		if (message != null) {
			m_children.add(message);
		} else {
			Cat.logError(new Exception("null child message"));
		}
		return this;
	}
	@Override
	public void complete() {
		try {
			if (isCompleted()) {
				// complete() was called more than once
				DefaultEvent event = new DefaultEvent("cat", "BadInstrument");
				event.setStatus("TransactionAlreadyCompleted");
				event.complete();
				addChild(event);
			} else {
				m_durationInMicro = (System.nanoTime() - m_durationStart) / 1000L;
				setCompleted(true);
				if (m_manager != null) {
					m_manager.end(this);
				}
			}
		} catch (Exception e) {
			// ignore
		}
	}
	@Override
	public List<Message> getChildren() {
		if (m_children == null) {
			return Collections.emptyList();
		}
		return m_children;
	}
	@Override
	public long getDurationInMicros() {
		if (m_durationInMicro >= 0) {
			return m_durationInMicro;
		} else { // if it's not completed explicitly
			long duration = 0;
			int len = m_children == null ? 0 : m_children.size();
			if (len > 0) {
				Message lastChild = m_children.get(len - 1);
				if (lastChild instanceof Transaction) {
					DefaultTransaction trx = (DefaultTransaction) lastChild;
					duration = (trx.getTimestamp() - getTimestamp()) * 1000L;
				} else {
					duration = (lastChild.getTimestamp() - getTimestamp()) * 1000L;
				}
			}
			return duration;
		}
	}
	@Override
	public long getDurationInMillis() {
		return getDurationInMicros() / 1000L;
	}
	protected MessageManager getManager() {
		return m_manager;
	}
	@Override
	public boolean hasChildren() {
		return m_children != null && m_children.size() > 0;
	}
	@Override
	public boolean isStandalone() {
		return m_standalone;
	}
	public void setDurationInMicros(long duration) {
		m_durationInMicro = duration;
	}
	public void setDurationInMillis(long duration) {
		m_durationInMicro = duration * 1000L;
	}
	public void setStandalone(boolean standalone) {
		m_standalone = standalone;
	}
	public void setDurationStart(long durationStart) {
		m_durationStart = durationStart;
	}
}

+ 129 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/MessageId.java

@ -0,0 +1,129 @@
package com.dianping.cat.message.internal;
import java.util.List;
import org.unidal.helper.Splitters;
public class MessageId {
	private static final long VERSION1_THRESHOLD = 1325347200000L; // Jan. 1 2012
	private String m_domain;
	private String m_ipAddressInHex;
	private long m_timestamp;
	private int m_index;
	public static MessageId parse(String messageId) {
		List<String> list = Splitters.by('-').split(messageId);
		int len = list.size();
		if (len >= 4) {
			String ipAddressInHex = list.get(len - 3);
			long timestamp = Long.parseLong(list.get(len - 2));
			int index = Integer.parseInt(list.get(len - 1));
			String domain;
			if (len > 4) { // allow domain contains '-'
				StringBuilder sb = new StringBuilder();
				for (int i = 0; i < len - 3; i++) {
					if (i > 0) {
						sb.append('-');
					}
					sb.append(list.get(i));
				}
				domain = sb.toString();
			} else {
				domain = list.get(0);
			}
			return new MessageId(domain, ipAddressInHex, timestamp, index);
		}
		throw new RuntimeException("Invalid message id format: " + messageId);
	}
	MessageId(String domain, String ipAddressInHex, long timestamp, int index) {
		m_domain = domain;
		m_ipAddressInHex = ipAddressInHex;
		m_timestamp = timestamp;
		m_index = index;
	}
	public String getDomain() {
		return m_domain;
	}
	public int getIndex() {
		return m_index;
	}
	public String getIpAddress() {
		StringBuilder sb = new StringBuilder();
		String local = m_ipAddressInHex;
		int length = local.length();
		for (int i = 0; i < length; i += 2) {
			char first = local.charAt(i);
			char next = local.charAt(i + 1);
			int temp = 0;
			if (first >= '0' && first <= '9') {
				temp += (first - '0') << 4;
			} else {
				temp += ((first - 'a') + 10) << 4;
			}
			if (next >= '0' && next <= '9') {
				temp += next - '0';
			} else {
				temp += (next - 'a') + 10;
			}
			if (sb.length() > 0) {
				sb.append('.');
			}
			sb.append(temp);
		}
		return sb.toString();
	}
	public String getIpAddressInHex() {
		return m_ipAddressInHex;
	}
	public long getTimestamp() {
		if (m_timestamp > VERSION1_THRESHOLD) {
			return m_timestamp;
		} else {
			return m_timestamp * 3600 * 1000L;
		}
	}
	public int getVersion() {
		if (m_timestamp > VERSION1_THRESHOLD) {
			return 1;
		} else {
			return 2;
		}
	}
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(m_domain.length() + 32);
		sb.append(m_domain);
		sb.append('-');
		sb.append(m_ipAddressInHex);
		sb.append('-');
		sb.append(m_timestamp);
		sb.append('-');
		sb.append(m_index);
		return sb.toString();
	}
}

+ 176 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/MessageIdFactory.java

@ -0,0 +1,176 @@
package com.dianping.cat.message.internal;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.unidal.helper.Splitters;
import com.dianping.cat.configuration.NetworkInterfaceManager;
public class MessageIdFactory {
	private volatile long m_timestamp = getTimestamp();
	private volatile AtomicInteger m_index;
	private String m_domain;
	private String m_ipAddress;
	private volatile boolean m_initialized;
	private MappedByteBuffer m_byteBuffer;
	private RandomAccessFile m_markFile;
	private static final long HOUR = 3600 * 1000L;
	private BlockingQueue<String> m_reusedIds = new LinkedBlockingQueue<String>(100000);
	public void close() {
		try {
			m_markFile.close();
		} catch (Exception e) {
			// ignore it
		}
	}
	private File createMarkFile(String domain) {
		File mark = new File("/data/appdatas/cat/", "cat-" + domain + ".mark");
		if (!mark.exists()) {
			boolean success = true;
			try {
				success = mark.createNewFile();
			} catch (Exception e) {
				success = false;
			}
			if (!success) {
				mark = createTempFile(domain);
			}
		} else if (!mark.canWrite()) {
			mark = createTempFile(domain);
		}
		return mark;
	}
	private File createTempFile(String domain) {
		String tmpDir = System.getProperty("java.io.tmpdir");
		File mark = new File(tmpDir, "cat-" + domain + ".mark");
		return mark;
	}
	public String getNextId() {
		String id = m_reusedIds.poll();
		if (id != null) {
			return id;
		} else {
			long timestamp = getTimestamp();
			if (timestamp != m_timestamp) {
				m_index = new AtomicInteger(0);
				m_timestamp = timestamp;
			}
			int index = m_index.getAndIncrement();
			StringBuilder sb = new StringBuilder(m_domain.length() + 32);
			sb.append(m_domain);
			sb.append('-');
			sb.append(m_ipAddress);
			sb.append('-');
			sb.append(timestamp);
			sb.append('-');
			sb.append(index);
			return sb.toString();
		}
	}
	protected long getTimestamp() {
		long timestamp = MilliSecondTimer.currentTimeMillis();
		return timestamp / HOUR; // version 2
	}
	public void initialize(String domain) throws IOException {
	    if (!m_initialized) {
		m_domain = domain;
		if (m_ipAddress == null) {
			String ip = NetworkInterfaceManager.INSTANCE.getLocalHostAddress();
			List<String> items = Splitters.by(".").noEmptyItem().split(ip);
			byte[] bytes = new byte[4];
			for (int i = 0; i < 4; i++) {
				bytes[i] = (byte) Integer.parseInt(items.get(i));
			}
			StringBuilder sb = new StringBuilder(bytes.length / 2);
			for (byte b : bytes) {
				sb.append(Integer.toHexString((b >> 4) & 0x0F));
				sb.append(Integer.toHexString(b & 0x0F));
			}
			m_ipAddress = sb.toString();
		}
		File mark = createMarkFile(domain);
		m_markFile = new RandomAccessFile(mark, "rw");
		m_byteBuffer = m_markFile.getChannel().map(MapMode.READ_WRITE, 0, 20);
		if (m_byteBuffer.limit() > 0) {
			int index = m_byteBuffer.getInt();
			long lastTimestamp = m_byteBuffer.getLong();
			if (lastTimestamp == m_timestamp) { // for same hour
				m_index = new AtomicInteger(index + 10000);
			} else {
				m_index = new AtomicInteger(0);
			}
		}
		
		m_initialized = true;
	    }
	    
	    saveMark();
	}
	protected void resetIndex() {
		m_index.set(0);
	}
	public void reuse(String id) {
		m_reusedIds.offer(id);
	}
	public void saveMark() {
		if (m_initialized) {
			try {
				m_byteBuffer.rewind();
				m_byteBuffer.putInt(m_index.get());
				m_byteBuffer.putLong(m_timestamp);
			} catch (Exception e) {
				// ignore it
			}
		}
	}
	public void setDomain(String domain) {
		m_domain = domain;
	}
	public void setIpAddress(String ipAddress) {
		m_ipAddress = ipAddress;
	}
}

+ 52 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/MilliSecondTimer.java

@ -0,0 +1,52 @@
package com.dianping.cat.message.internal;
import java.util.concurrent.locks.LockSupport;
/**
 * This timer provides milli-second precise system time.
 */
public class MilliSecondTimer {
	private static long m_baseTime;
	private static long m_startNanoTime;
	private static boolean m_isWindows = false;
	public static long currentTimeMillis() {
		if (m_isWindows) {
			if (m_baseTime == 0) {
				initialize();
			}
			long elipsed = (long) ((System.nanoTime() - m_startNanoTime) / 1e6);
			return m_baseTime + elipsed;
		} else {
			return System.currentTimeMillis();
		}
	}
	public static void initialize() {
		String os = System.getProperty("os.name");
		if (os.startsWith("Windows")) {
			m_isWindows = true;
			m_baseTime = System.currentTimeMillis();
			while (true) {
				LockSupport.parkNanos(100000); // 0.1 ms
				long millis = System.currentTimeMillis();
				if (millis != m_baseTime) {
					m_baseTime = millis;
					m_startNanoTime = System.nanoTime();
					break;
				}
			}
		} else {
			m_baseTime = System.currentTimeMillis();
			m_startNanoTime = System.nanoTime();
		}
	}
}

+ 356 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/MockMessageBuilder.java

@ -0,0 +1,356 @@
package com.dianping.cat.message.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Metric;
import com.dianping.cat.message.Transaction;
public abstract class MockMessageBuilder {
	private Stack<TransactionHolder> m_stack = new Stack<TransactionHolder>();
	public final Message build() {
		try {
			return define().build();
		} finally {
			m_stack.clear();
		}
	}
	public abstract MessageHolder define();
	protected EventHolder e(String type, String name) {
		EventHolder e = new EventHolder(type, name);
		TransactionHolder parent = m_stack.isEmpty() ? null : m_stack.peek();
		if (parent != null) {
			e.setTimestampInMicros(parent.getCurrentTimestampInMicros());
		}
		return e;
	}
	protected EventHolder e(String type, String name, String data) {
		EventHolder e = new EventHolder(type, name, data);
		TransactionHolder parent = m_stack.isEmpty() ? null : m_stack.peek();
		if (parent != null) {
			e.setTimestampInMicros(parent.getCurrentTimestampInMicros());
		}
		return e;
	}
	protected HeartbeatHolder h(String type, String name) {
		HeartbeatHolder h = new HeartbeatHolder(type, name);
		TransactionHolder parent = m_stack.isEmpty() ? null : m_stack.peek();
		if (parent != null) {
			h.setTimestampInMicros(parent.getCurrentTimestampInMicros());
		}
		return h;
	}
	protected MetricHolder m(String type, String name) {
		MetricHolder e = new MetricHolder(type, name);
		TransactionHolder parent = m_stack.isEmpty() ? null : m_stack.peek();
		if (parent != null) {
			e.setTimestampInMicros(parent.getCurrentTimestampInMicros());
		}
		return e;
	}
	protected MetricHolder m(String type, String name, String data) {
		MetricHolder e = new MetricHolder(type, name, data);
		TransactionHolder parent = m_stack.isEmpty() ? null : m_stack.peek();
		if (parent != null) {
			e.setTimestampInMicros(parent.getCurrentTimestampInMicros());
		}
		return e;
	}
	protected TransactionHolder t(String type, String name, long durationInMillis) {
		TransactionHolder t = new TransactionHolder(type, name, durationInMillis);
		TransactionHolder parent = m_stack.isEmpty() ? null : m_stack.peek();
		if (parent != null) {
			t.setTimestampInMicros(parent.getCurrentTimestampInMicros());
		}
		m_stack.push(t);
		return t;
	}
	protected TransactionHolder t(String type, String name, String data, long durationInMillis) {
		TransactionHolder t = new TransactionHolder(type, name, data, durationInMillis);
		TransactionHolder parent = m_stack.isEmpty() ? null : m_stack.peek();
		if (parent != null) {
			t.setTimestampInMicros(parent.getCurrentTimestampInMicros());
		}
		m_stack.push(t);
		return t;
	}
	protected static abstract class AbstractMessageHolder implements MessageHolder {
		private String m_type;
		private String m_name;
		private String m_data;
		private long m_timestampInMicros;
		private String m_status = "0";
		public AbstractMessageHolder(String type, String name) {
			m_type = type;
			m_name = name;
		}
		public AbstractMessageHolder(String type, String name, String data) {
			m_type = type;
			m_name = name;
			m_data = data;
		}
		public void addData(String key, String value) {
			if (m_data == null) {
				m_data = key + "=" + value;
			} else {
				m_data = m_data + "&" + key + "=" + value;
			}
		}
		public String getData() {
			return m_data;
		}
		public String getName() {
			return m_name;
		}
		public String getStatus() {
			return m_status;
		}
		@Override
		public long getTimestampInMicros() {
			return m_timestampInMicros;
		}
		public long getTimestampInMillis() {
			return m_timestampInMicros / 1000;
		}
		public String getType() {
			return m_type;
		}
		public void setStatus(String status) {
			m_status = status;
		}
		@Override
		public void setTimestampInMicros(long timestampInMicros) {
			m_timestampInMicros = timestampInMicros;
		}
	}
	public static class EventHolder extends AbstractMessageHolder {
		private DefaultEvent m_event;
		public EventHolder(String type, String name) {
			super(type, name);
		}
		public EventHolder(String type, String name, String data) {
			super(type, name, data);
		}
		@Override
		public Event build() {
			m_event = new DefaultEvent(getType(), getName(), null);
			m_event.setTimestamp(getTimestampInMillis());
			m_event.setStatus(getStatus());
			m_event.addData(getData());
			m_event.complete();
			return m_event;
		}
		public EventHolder status(String status) {
			setStatus(status);
			return this;
		}
	}
	protected static class HeartbeatHolder extends AbstractMessageHolder {
		private DefaultHeartbeat m_heartbeat;
		public HeartbeatHolder(String type, String name) {
			super(type, name);
		}
		@Override
		public Heartbeat build() {
			m_heartbeat = new DefaultHeartbeat(getType(), getName());
			m_heartbeat.setTimestamp(getTimestampInMillis());
			m_heartbeat.setStatus(getStatus());
			m_heartbeat.complete();
			return m_heartbeat;
		}
		public HeartbeatHolder status(String status) {
			setStatus(status);
			return this;
		}
	}
	protected static interface MessageHolder {
		public Message build();
		public long getTimestampInMicros();
		public void setTimestampInMicros(long timestampInMicros);
	}
	protected static class MetricHolder extends AbstractMessageHolder {
		private DefaultMetric m_metric;
		public MetricHolder(String type, String name) {
			super(type, name);
		}
		public MetricHolder(String type, String name, String data) {
			super(type, name, data);
		}
		@Override
		public Metric build() {
			m_metric = new DefaultMetric(getType(), getName());
			m_metric.setTimestamp(getTimestampInMillis());
			m_metric.setStatus(getStatus());
			m_metric.addData(getData());
			m_metric.complete();
			return m_metric;
		}
		public MetricHolder status(String status) {
			setStatus(status);
			return this;
		}
	}
	protected class TransactionHolder extends AbstractMessageHolder {
		private long m_durationInMicros;
		private long m_currentTimestampInMicros;
		private List<MessageHolder> m_children = new ArrayList<MessageHolder>();
		private DefaultTransaction m_transaction;
		private long m_markTimestampInMicros;
		public TransactionHolder(String type, String name, long durationInMicros) {
			super(type, name);
			m_durationInMicros = durationInMicros;
		}
		public TransactionHolder(String type, String name, String data, long durationInMicros) {
			super(type, name, data);
			m_durationInMicros = durationInMicros;
		}
		public TransactionHolder after(long periodInMicros) {
			m_currentTimestampInMicros += periodInMicros;
			return this;
		}
		public TransactionHolder at(long timestampInMillis) {
			m_currentTimestampInMicros = timestampInMillis * 1000;
			setTimestampInMicros(m_currentTimestampInMicros);
			return this;
		}
		@Override
		public Transaction build() {
			m_transaction = new DefaultTransaction(getType(), getName(), null);
			m_transaction.setTimestamp(getTimestampInMillis());
			for (MessageHolder child : m_children) {
				m_transaction.addChild(child.build());
			}
			m_transaction.setStatus(getStatus());
			m_transaction.addData(getData());
			m_transaction.complete();
			m_transaction.setDurationInMicros(m_durationInMicros);
			return m_transaction;
		}
		public TransactionHolder child(MessageHolder child) {
			if (child instanceof TransactionHolder) {
				m_currentTimestampInMicros += ((TransactionHolder) child).getDurationInMicros();
				m_stack.pop();
			}
			m_children.add(child);
			return this;
		}
		public TransactionHolder data(String key, String value) {
			addData(key, value);
			return this;
		}
		public long getCurrentTimestampInMicros() {
			return m_currentTimestampInMicros;
		}
		public long getDurationInMicros() {
			return m_durationInMicros;
		}
		public TransactionHolder mark() {
			m_markTimestampInMicros = m_currentTimestampInMicros;
			return this;
		}
		public TransactionHolder reset() {
			m_currentTimestampInMicros = m_markTimestampInMicros;
			return this;
		}
		@Override
		public void setTimestampInMicros(long timestampInMicros) {
			super.setTimestampInMicros(timestampInMicros);
			m_currentTimestampInMicros = timestampInMicros;
		}
		public TransactionHolder status(String status) {
			setStatus(status);
			return this;
		}
	}
}

+ 116 - 0
cat-client/src/main/java/com/dianping/cat/message/internal/NullMessage.java

@ -0,0 +1,116 @@
package com.dianping.cat.message.internal;
import java.util.Collections;
import java.util.List;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Metric;
import com.dianping.cat.message.Trace;
import com.dianping.cat.message.Transaction;
public enum NullMessage implements Transaction, Event, Metric, Trace, Heartbeat {
	TRANSACTION,
	EVENT,
	METRIC,
	TRACE,
	HEARTBEAT;
	@Override
	public Transaction addChild(Message message) {
		return this;
	}
	@Override
	public void addData(String keyValuePairs) {
	}
	@Override
	public void addData(String key, Object value) {
	}
	@Override
	public void complete() {
	}
	@Override
	public List<Message> getChildren() {
		return Collections.emptyList();
	}
	@Override
	public Object getData() {
		return null;
	}
	@Override
	public long getDurationInMicros() {
		return 0;
	}
	@Override
	public long getDurationInMillis() {
		return 0;
	}
	@Override
	public String getName() {
		throw new UnsupportedOperationException();
	}
	public String getParentMessageId() {
		return null;
	}
	public String getRootMessageId() {
		return null;
	}
	@Override
	public String getStatus() {
		throw new UnsupportedOperationException();
	}
	@Override
	public long getTimestamp() {
		throw new UnsupportedOperationException();
	}
	@Override
	public String getType() {
		throw new UnsupportedOperationException();
	}
	@Override
	public boolean hasChildren() {
		return false;
	}
	@Override
	public boolean isCompleted() {
		return true;
	}
	@Override
	public boolean isStandalone() {
		return true;
	}
	@Override
	public boolean isSuccess() {
		return true;
	}
	@Override
	public void setStatus(String status) {
	}
	@Override
	public void setStatus(Throwable e) {
	}
}

+ 470 - 0
cat-client/src/main/java/com/dianping/cat/message/io/ChannelManager.java

@ -0,0 +1,470 @@
package com.dianping.cat.message.io;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.unidal.helper.Files;
import org.unidal.helper.Splitters;
import org.unidal.helper.Threads.Task;
import org.unidal.helper.Urls;
import org.unidal.lookup.logging.Logger;
import org.unidal.lookup.util.StringUtils;
import org.unidal.tuple.Pair;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.configuration.KVConfig;
import com.dianping.cat.message.internal.MessageIdFactory;
import com.dianping.cat.message.spi.MessageQueue;
import com.site.helper.JsonBuilder;
public class ChannelManager implements Task {
	private ClientConfigManager m_configManager;
	private Bootstrap m_bootstrap;
	private Logger m_logger;
	private boolean m_active = true;
	private int m_retriedTimes = 0;
	private int m_count = -10;
	private volatile double m_sample = 1d;
	private MessageQueue m_queue;
	private ChannelHolder m_activeChannelHolder;
	private MessageIdFactory m_idfactory;
	private JsonBuilder m_jsonBuilder = new JsonBuilder();
	public ChannelManager(Logger logger, List<InetSocketAddress> serverAddresses, MessageQueue queue,
			ClientConfigManager configManager, MessageIdFactory idFactory) {
		m_logger = logger;
		m_queue = queue;
		m_configManager = configManager;
		m_idfactory = idFactory;
		EventLoopGroup group = new NioEventLoopGroup(1, new ThreadFactory() {
			@Override
			public Thread newThread(Runnable r) {
				Thread t = new Thread(r);
				t.setDaemon(true);
				return t;
			}
		});
		Bootstrap bootstrap = new Bootstrap();
		bootstrap.group(group).channel(NioSocketChannel.class);
		bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
		bootstrap.handler(new ChannelInitializer<Channel>() {
			@Override
			protected void initChannel(Channel ch) throws Exception {
			}
		});
		m_bootstrap = bootstrap;
		String serverConfig = loadServerConfig();
		if (StringUtils.isNotEmpty(serverConfig)) {
			List<InetSocketAddress> configedAddresses = parseSocketAddress(serverConfig);
			ChannelHolder holder = initChannel(configedAddresses, serverConfig);
			if (holder != null) {
				m_activeChannelHolder = holder;
			} else {
				m_activeChannelHolder = new ChannelHolder();
				m_activeChannelHolder.setServerAddresses(configedAddresses);
			}
		} else {
			ChannelHolder holder = initChannel(serverAddresses, null);
			if (holder != null) {
				m_activeChannelHolder = holder;
			} else {
				m_activeChannelHolder = new ChannelHolder();
				m_activeChannelHolder.setServerAddresses(serverAddresses);
				m_logger.error("error when init cat module due to error config xml in /data/appdatas/cat/client.xml");
			}
		}
	}
	public ChannelFuture channel() {
		if (m_activeChannelHolder != null) {
			return m_activeChannelHolder.getActiveFuture();
		} else {
			return null;
		}
	}
	private void checkServerChanged() {
		if (shouldCheckServerConfig(++m_count)) {
			Pair<Boolean, String> pair = routerConfigChanged();
			if (pair.getKey()) {
				String servers = pair.getValue();
				List<InetSocketAddress> serverAddresses = parseSocketAddress(servers);
				ChannelHolder newHolder = initChannel(serverAddresses, servers);
				if (newHolder != null) {
					if (newHolder.isConnectChanged()) {
						ChannelHolder last = m_activeChannelHolder;
						m_activeChannelHolder = newHolder;
						closeChannelHolder(last);
						m_logger.info("switch active channel to " + m_activeChannelHolder);
					} else {
						m_activeChannelHolder = newHolder;
					}
				}
			}
		}
	}
	private void closeChannel(ChannelFuture channel) {
		try {
			if (channel != null) {
				SocketAddress ip = channel.channel().remoteAddress();
				if (ip != null) {
					m_logger.info("close channel " + ip);
				}
				channel.channel().close();
			}
		} catch (Exception e) {
			// ignore
		}
	}
	private void closeChannelHolder(ChannelHolder channelHolder) {
		try {
			ChannelFuture channel = channelHolder.getActiveFuture();
			closeChannel(channel);
			channelHolder.setActiveIndex(-1);
		} catch (Exception e) {
			// ignore
		}
	}
	private ChannelFuture createChannel(InetSocketAddress address) {
		ChannelFuture future = null;
		try {
			future = m_bootstrap.connect(address);
			future.awaitUninterruptibly(100, TimeUnit.MILLISECONDS); // 100 ms
			if (!future.isSuccess()) {
				m_logger.error("Error when try connecting to " + address);
				closeChannel(future);
			} else {
				m_logger.info("Connected to CAT server at " + address);
				return future;
			}
		} catch (Throwable e) {
			m_logger.error("Error when connect server " + address.getAddress(), e);
			if (future != null) {
				closeChannel(future);
			}
		}
		return null;
	}
	private void doubleCheckActiveServer(ChannelFuture activeFuture) {
		try {
			if (isChannelStalled(activeFuture) || isChannelDisabled(activeFuture)) {
				closeChannelHolder(m_activeChannelHolder);
			}
		} catch (Throwable e) {
			m_logger.error(e.getMessage(), e);
		}
	}
	@Override
	public String getName() {
		return "TcpSocketSender-ChannelManager";
	}
	public double getSample() {
		return m_sample;
	}
	private ChannelHolder initChannel(List<InetSocketAddress> addresses, String serverConfig) {
		try {
			int len = addresses.size();
			for (int i = 0; i < len; i++) {
				InetSocketAddress address = addresses.get(i);
				String hostAddress = address.getAddress().getHostAddress();
				ChannelHolder holder = null;
				if (m_activeChannelHolder != null && hostAddress.equals(m_activeChannelHolder.getIp())) {
					holder = new ChannelHolder();
					holder.setActiveFuture(m_activeChannelHolder.getActiveFuture()).setConnectChanged(false);
				} else {
					ChannelFuture future = createChannel(address);
					if (future != null) {
						holder = new ChannelHolder();
						holder.setActiveFuture(future).setConnectChanged(true);
					}
				}
				if (holder != null) {
					holder.setActiveIndex(i).setIp(hostAddress);
					holder.setActiveServerConfig(serverConfig).setServerAddresses(addresses);
					m_logger.info("success when init CAT server, new active holder" + holder.toString());
					return holder;
				}
			}
		} catch (Exception e) {
			m_logger.error(e.getMessage(), e);
		}
		try {
			StringBuilder sb = new StringBuilder();
			for (InetSocketAddress address : addresses) {
				sb.append(address.toString()).append(";");
			}
			m_logger.info("Error when init CAT server " + sb.toString());
		} catch (Exception e) {
			// ignore
		}
		return null;
	}
	private boolean isChannelDisabled(ChannelFuture activeFuture) {
		return activeFuture != null && !activeFuture.channel().isOpen();
	}
	private boolean isChannelStalled(ChannelFuture activeFuture) {
		m_retriedTimes++;
		int size = m_queue.size();
		boolean stalled = activeFuture != null && size >= TcpSocketSender.getQueueSize() - 10;
		if (stalled) {
			if (m_retriedTimes >= 5) {
				m_retriedTimes = 0;
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
	private String loadServerConfig() {
		try {
			String url = m_configManager.getServerConfigUrl();
			InputStream inputstream = Urls.forIO().readTimeout(2000).connectTimeout(1000).openStream(url);
			String content = Files.forIO().readFrom(inputstream, "utf-8");
			KVConfig routerConfig = (KVConfig) m_jsonBuilder.parse(content.trim(), KVConfig.class);
			String current = routerConfig.getValue("routers");
			m_sample = Double.valueOf(routerConfig.getValue("sample").trim());
			return current.trim();
		} catch (Exception e) {
			// ignore
		}
		return null;
	}
	private List<InetSocketAddress> parseSocketAddress(String content) {
		try {
			List<String> strs = Splitters.by(";").noEmptyItem().split(content);
			List<InetSocketAddress> address = new ArrayList<InetSocketAddress>();
			for (String str : strs) {
				List<String> items = Splitters.by(":").noEmptyItem().split(str);
				address.add(new InetSocketAddress(items.get(0), Integer.parseInt(items.get(1))));
			}
			return address;
		} catch (Exception e) {
			m_logger.error(e.getMessage(), e);
		}
		return new ArrayList<InetSocketAddress>();
	}
	private void reconnectDefaultServer(ChannelFuture activeFuture, List<InetSocketAddress> serverAddresses) {
		try {
			int reconnectServers = m_activeChannelHolder.getActiveIndex();
			if (reconnectServers == -1) {
				reconnectServers = serverAddresses.size();
			}
			for (int i = 0; i < reconnectServers; i++) {
				ChannelFuture future = createChannel(serverAddresses.get(i));
				if (future != null) {
					ChannelFuture lastFuture = activeFuture;
					m_activeChannelHolder.setActiveFuture(future);
					m_activeChannelHolder.setActiveIndex(i);
					closeChannel(lastFuture);
					break;
				}
			}
		} catch (Throwable e) {
			m_logger.error(e.getMessage(), e);
		}
	}
	private Pair<Boolean, String> routerConfigChanged() {
		String current = loadServerConfig();
		if (!StringUtils.isEmpty(current) && !current.equals(m_activeChannelHolder.getActiveServerConfig())) {
			return new Pair<Boolean, String>(true, current);
		} else {
			return new Pair<Boolean, String>(false, current);
		}
	}
	@Override
	public void run() {
		while (m_active) {
			// make save message id index asyc
			m_idfactory.saveMark();
			checkServerChanged();
			ChannelFuture activeFuture = m_activeChannelHolder.getActiveFuture();
			List<InetSocketAddress> serverAddresses = m_activeChannelHolder.getServerAddresses();
			doubleCheckActiveServer(activeFuture);
			reconnectDefaultServer(activeFuture, serverAddresses);
			try {
				Thread.sleep(10 * 1000L); // check every 10 seconds
			} catch (InterruptedException e) {
				// ignore
			}
		}
	}
	private boolean shouldCheckServerConfig(int count) {
		int duration = 30;
		if (count % duration == 0 || m_activeChannelHolder.getActiveIndex() == -1) {
			return true;
		} else {
			return false;
		}
	}
	@Override
	public void shutdown() {
		m_active = false;
	}
	public class ClientMessageHandler extends SimpleChannelInboundHandler<Object> {
		@Override
		protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
			m_logger.info("receiver msg from server:" + msg);
		}
	}
	public static class ChannelHolder {
		private ChannelFuture m_activeFuture;
		private int m_activeIndex = -1;
		private String m_activeServerConfig;
		private List<InetSocketAddress> m_serverAddresses;
		private String m_ip;
		private boolean m_connectChanged;
		public ChannelFuture getActiveFuture() {
			return m_activeFuture;
		}
		public int getActiveIndex() {
			return m_activeIndex;
		}
		public String getActiveServerConfig() {
			return m_activeServerConfig;
		}
		public String getIp() {
			return m_ip;
		}
		public List<InetSocketAddress> getServerAddresses() {
			return m_serverAddresses;
		}
		public boolean isConnectChanged() {
			return m_connectChanged;
		}
		public ChannelHolder setActiveFuture(ChannelFuture activeFuture) {
			m_activeFuture = activeFuture;
			return this;
		}
		public ChannelHolder setActiveIndex(int activeIndex) {
			m_activeIndex = activeIndex;
			return this;
		}
		public ChannelHolder setActiveServerConfig(String activeServerConfig) {
			m_activeServerConfig = activeServerConfig;
			return this;
		}
		public ChannelHolder setConnectChanged(boolean connectChanged) {
			m_connectChanged = connectChanged;
			return this;
		}
		public ChannelHolder setIp(String ip) {
			m_ip = ip;
			return this;
		}
		public ChannelHolder setServerAddresses(List<InetSocketAddress> serverAddresses) {
			m_serverAddresses = serverAddresses;
			return this;
		}
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("active future :").append(m_activeFuture.channel().remoteAddress());
			sb.append(" index:").append(m_activeIndex);
			sb.append(" ip:").append(m_ip);
			sb.append(" server config:").append(m_activeServerConfig);
			return sb.toString();
		}
	}
}

+ 57 - 0
cat-client/src/main/java/com/dianping/cat/message/io/DefaultMessageQueue.java

@ -0,0 +1,57 @@
package com.dianping.cat.message.io;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import com.dianping.cat.message.spi.MessageQueue;
import com.dianping.cat.message.spi.MessageTree;
public class DefaultMessageQueue implements MessageQueue {
	private BlockingQueue<MessageTree> m_queue;
	private Random rand = new Random();
	public DefaultMessageQueue(int size) {
		m_queue = new LinkedBlockingQueue<MessageTree>(size);
	}
	@Override
	public boolean offer(MessageTree tree) {
		return m_queue.offer(tree);
	}
	@Override
	public boolean offer(MessageTree tree, double sampleRatio) {
		if (tree.isSample() && sampleRatio < 1.0) {
			if (sampleRatio > 0) {
				if (rand.nextInt(100) < 100 * sampleRatio) {
					return offer(tree);
				}
			}
			return false;
		} else {
			return offer(tree);
		}
	}
	@Override
	public MessageTree peek() {
		return m_queue.peek();
	}
	@Override
	public MessageTree poll() {
		try {
			return m_queue.poll(5, TimeUnit.MILLISECONDS);
		} catch (InterruptedException e) {
			return null;
		}
	}
	@Override
	public int size() {
		return m_queue.size();
	}
}

+ 62 - 0
cat-client/src/main/java/com/dianping/cat/message/io/DefaultTransportManager.java

@ -0,0 +1,62 @@
package com.dianping.cat.message.io;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.unidal.lookup.annotation.Inject;
import org.unidal.lookup.extension.Initializable;
import org.unidal.lookup.extension.InitializationException;
import org.unidal.lookup.logging.LogEnabled;
import org.unidal.lookup.logging.Logger;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.configuration.client.entity.Server;
public class DefaultTransportManager implements TransportManager, Initializable, LogEnabled {
	@Inject
	private ClientConfigManager m_configManager;
	@Inject
	private TcpSocketSender m_tcpSocketSender;
	private Logger m_logger;
	@Override
	public void enableLogging(Logger logger) {
		m_logger = logger;
	}
	@Override
	public MessageSender getSender() {
		return m_tcpSocketSender;
	}
	@Override
	public void initialize() throws InitializationException {
		List<Server> servers = m_configManager.getServers();
		if (!m_configManager.isCatEnabled()) {
			m_tcpSocketSender = null;
			m_logger.warn("CAT was DISABLED due to not initialized yet!");
		} else {
			List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
			for (Server server : servers) {
				if (server.isEnabled()) {
					addresses.add(new InetSocketAddress(server.getIp(), server.getPort()));
				}
			}
			m_logger.info("Remote CAT servers: " + addresses);
			if (addresses.isEmpty()) {
				throw new RuntimeException("All servers in configuration are disabled!\r\n" + servers);
			} else {
				m_tcpSocketSender.setServerAddresses(addresses);
				m_tcpSocketSender.initialize();
			}
		}
	}
}

+ 11 - 0
cat-client/src/main/java/com/dianping/cat/message/io/MessageSender.java

@ -0,0 +1,11 @@
package com.dianping.cat.message.io;
import com.dianping.cat.message.spi.MessageTree;
public interface MessageSender {
	public void initialize();
	public void send(MessageTree tree);
	public void shutdown();
}

+ 321 - 0
cat-client/src/main/java/com/dianping/cat/message/io/TcpSocketSender.java

@ -0,0 +1,321 @@
package com.dianping.cat.message.io;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.unidal.helper.Threads;
import org.unidal.helper.Threads.Task;
import org.unidal.lookup.annotation.Inject;
import org.unidal.lookup.logging.LogEnabled;
import org.unidal.lookup.logging.Logger;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.internal.DefaultTransaction;
import com.dianping.cat.message.internal.MessageIdFactory;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.MessageQueue;
import com.dianping.cat.message.spi.MessageStatistics;
import com.dianping.cat.message.spi.MessageTree;
import com.dianping.cat.message.spi.internal.DefaultMessageTree;
public class TcpSocketSender implements Task, MessageSender, LogEnabled {
	public static final String ID = "tcp-socket-sender";
	private static final int MAX_CHILD_NUMBER = 200;
	private static final long HOUR = 1000 * 60 * 60L;
	@Inject
	private MessageCodec m_codec;
	@Inject
	private MessageStatistics m_statistics;
	@Inject
	private ClientConfigManager m_configManager;
	@Inject
	private MessageIdFactory m_factory;
	private MessageQueue m_queue;
	private MessageQueue m_atomicTrees;
	private List<InetSocketAddress> m_serverAddresses;
	private ChannelManager m_manager;
	private Logger m_logger;
	private transient boolean m_active;
	private AtomicInteger m_errors = new AtomicInteger();
	private AtomicInteger m_attempts = new AtomicInteger();
	public static int getQueueSize() {
		String size = System.getProperty("queue.size", "1000");
		return Integer.parseInt(size);
	}
	private boolean checkWritable(ChannelFuture future) throws InterruptedException {
		boolean isWriteable = false;
		Channel channel = future.channel();
		if (channel.isOpen()) {
			if (channel.isActive() && channel.isWritable()) {
				isWriteable = true;
			} else {
				int count = m_attempts.incrementAndGet();
				if (count % 1000 == 0) {
					m_logger.info("Netty write buffer is full! Cat will auto reconnect,Attempts: " + count);
				}
				TimeUnit.MILLISECONDS.sleep(5);
			}
		}
		return isWriteable;
	}
	@Override
	public void enableLogging(Logger logger) {
		m_logger = logger;
	}
	@Override
	public String getName() {
		return "TcpSocketSender";
	}
	@Override
	public void initialize() {
		int len = getQueueSize();
		m_queue = new DefaultMessageQueue(len);
		m_atomicTrees = new DefaultMessageQueue(len);
		m_manager = new ChannelManager(m_logger, m_serverAddresses, m_queue, m_configManager, m_factory);
		Threads.forGroup("cat").start(this);
		Threads.forGroup("cat").start(m_manager);
		Threads.forGroup("cat").start(new MergeAtomicTask());
	}
	private boolean isAtomicMessage(MessageTree tree) {
		Message message = tree.getMessage();
		if (message instanceof Transaction) {
			String type = message.getType();
			if (type.startsWith("Cache.") || "SQL".equals(type)) {
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}
	}
	private void logQueueFullInfo(MessageTree tree) {
		if (m_statistics != null) {
			m_statistics.onOverflowed(tree);
		}
		int count = m_errors.incrementAndGet();
		if (count % 1000 == 0 || count == 1) {
			m_logger.error("Message queue is full in tcp socket sender! Count: " + count);
		}
		tree = null;
	}
	private MessageTree mergeTree(MessageQueue trees) {
		int max = MAX_CHILD_NUMBER;
		DefaultTransaction t = new DefaultTransaction("System", "MergeTree", null);
		MessageTree first = trees.poll();
		t.setStatus(Transaction.SUCCESS);
		t.setCompleted(true);
		t.addChild(first.getMessage());
		t.setTimestamp(first.getMessage().getTimestamp());
		long lastTimestamp = 0;
		long lastDuration = 0;
		while (max >= 0) {
			MessageTree tree = trees.poll();
			if (tree == null) {
				t.setDurationInMillis(lastTimestamp - t.getTimestamp() + lastDuration);
				break;
			}
			lastTimestamp = tree.getMessage().getTimestamp();
			if (tree.getMessage() instanceof DefaultTransaction) {
				lastDuration = ((DefaultTransaction) tree.getMessage()).getDurationInMillis();
			} else {
				lastDuration = 0;
			}
			t.addChild(tree.getMessage());
			m_factory.reuse(tree.getMessageId());
			max--;
		}
		((DefaultMessageTree) first).setMessage(t);
		return first;
	}
	@Override
	public void run() {
		m_active = true;
		try {
			while (m_active) {
				ChannelFuture channel = m_manager.channel();
				if (channel != null && checkWritable(channel)) {
					try {
						MessageTree tree = m_queue.poll();
						if (tree != null) {
							sendInternal(tree);
							tree.setMessage(null);
						}
					} catch (Throwable t) {
						m_logger.error("Error when sending message over TCP socket!", t);
					}
				} else {
					long current = System.currentTimeMillis();
					long oldTimestamp = current - HOUR;
					while (true) {
						try {
							MessageTree tree = m_queue.peek();
							if (tree != null && tree.getMessage().getTimestamp() < oldTimestamp) {
								MessageTree discradTree = m_queue.poll();
								if (discradTree != null) {
									m_statistics.onOverflowed(discradTree);
								}
							} else {
								break;
							}
						} catch (Exception e) {
							m_logger.error(e.getMessage(), e);
							break;
						}
					}
					TimeUnit.MILLISECONDS.sleep(5);
				}
			}
		} catch (InterruptedException e) {
			// ignore it
			m_active = false;
		}
	}
	@Override
	public void send(MessageTree tree) {
		if (isAtomicMessage(tree)) {
			boolean result = m_atomicTrees.offer(tree, m_manager.getSample());
			if (!result) {
				logQueueFullInfo(tree);
			}
		} else {
			boolean result = m_queue.offer(tree, m_manager.getSample());
			if (!result) {
				logQueueFullInfo(tree);
			}
		}
	}
	private void sendInternal(MessageTree tree) {
		ChannelFuture future = m_manager.channel();
		ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(10 * 1024); // 10K
		m_codec.encode(tree, buf);
		int size = buf.readableBytes();
		Channel channel = future.channel();
		channel.writeAndFlush(buf);
		if (m_statistics != null) {
			m_statistics.onBytes(size);
		}
	}
	public void setServerAddresses(List<InetSocketAddress> serverAddresses) {
		m_serverAddresses = serverAddresses;
	}
	// merge atomic messages for 30 seconds or 200 messages
	private boolean shouldMerge(MessageQueue trees) {
		MessageTree tree = trees.peek();
		if (tree != null) {
			long firstTime = tree.getMessage().getTimestamp();
			int maxDuration = 1000 * 30;
			if (System.currentTimeMillis() - firstTime > maxDuration || trees.size() >= MAX_CHILD_NUMBER) {
				return true;
			}
		}
		return false;
	}
	@Override
	public void shutdown() {
		m_active = false;
		m_manager.shutdown();
	}
	public class MergeAtomicTask implements Task {
		@Override
		public String getName() {
			return "merge-atomic-task";
		}
		@Override
		public void run() {
			while (true) {
				if (shouldMerge(m_atomicTrees)) {
					MessageTree tree = mergeTree(m_atomicTrees);
					boolean result = m_queue.offer(tree);
					if (!result) {
						logQueueFullInfo(tree);
					}
				} else {
					try {
						Thread.sleep(5);
					} catch (InterruptedException e) {
						break;
					}
				}
			}
		}
		@Override
		public void shutdown() {
		}
	}
}

+ 5 - 0
cat-client/src/main/java/com/dianping/cat/message/io/TransportManager.java

@ -0,0 +1,5 @@
package com.dianping.cat.message.io;
public interface TransportManager {
	public MessageSender getSender();
}

+ 11 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/MessageCodec.java

@ -0,0 +1,11 @@
package com.dianping.cat.message.spi;
import io.netty.buffer.ByteBuf;
public interface MessageCodec {
	public MessageTree decode(ByteBuf buf);
	public void decode(ByteBuf buf, MessageTree tree);
	public void encode(MessageTree tree, ByteBuf buf);
}

+ 96 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/MessageManager.java

@ -0,0 +1,96 @@
package com.dianping.cat.message.spi;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
/**
 * Message manager to help build CAT message.
 * <p>
 * 
 * Notes: This method is reserved for internal usage only. Application developer should never call this method directly.
 */
public interface MessageManager {
	public void add(Message message);
	/**
	 * Be triggered when a transaction ends, whatever it's the root transaction or nested transaction. However, if it's
	 * the root transaction then it will be flushed to back-end CAT server asynchronously.
	 * <p>
	 * 
	 * @param transaction
	 */
	public void end(Transaction transaction);
	/**
	 * Get peek transaction for current thread.
	 * 
	 * @return peek transaction for current thread, null if no transaction there.
	 */
	public Transaction getPeekTransaction();
	/**
	 * Get thread local message information.
	 * 
	 * @return message tree, null means current thread is not setup correctly.
	 */
	public MessageTree getThreadLocalMessageTree();
	/**
	 * Check if the thread context is setup or not.
	 * 
	 * @return true if the thread context is setup, false otherwise
	 */
	public boolean hasContext();
	/**
	 * Check if current context logging is enabled or disabled.
	 * 
	 * @return true if current context is enabled
	 */
	public boolean isMessageEnabled();
	/**
	 * Check if CAT logging is enabled or disabled.
	 * 
	 * @return true if CAT is enabled
	 */
	public boolean isCatEnabled();
	/**
	 * Check if CAT trace mode is enabled or disabled.
	 * 
	 * @return true if CAT is trace mode
	 */
	public boolean isTraceMode();
	/**
	 * Do cleanup for current thread environment in order to release resources in thread local objects.
	 */
	public void reset();
	/**
	 * Set CAT trace mode.
	 * 
	 */
	public void setTraceMode(boolean traceMode);
	/**
	 * Do setup for current thread environment in order to prepare thread local objects.
	 */
	public void setup();
	/**
	 * Be triggered when a new transaction starts, whatever it's the root transaction or nested transaction.
	 * 
	 * @param transaction
	 * @param forked
	 */
	public void start(Transaction transaction, boolean forked);
	/**
	 * get domain
	 * 
	 */
	public String getDomain();
}

+ 14 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/MessageQueue.java

@ -0,0 +1,14 @@
package com.dianping.cat.message.spi;
public interface MessageQueue {
	public boolean offer(MessageTree tree);
	public boolean offer(MessageTree tree, double sampleRatio);
	public MessageTree peek();
	public MessageTree poll();
	// the current size of the queue
	public int size();
}

+ 14 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/MessageStatistics.java

@ -0,0 +1,14 @@
package com.dianping.cat.message.spi;
public interface MessageStatistics {
	public long getBytes();
	public long getOverflowed();
	public long getProduced();
	public void onBytes(int size);
	public void onOverflowed(MessageTree tree);
}

+ 55 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/MessageTree.java

@ -0,0 +1,55 @@
package com.dianping.cat.message.spi;
import com.dianping.cat.message.Message;
public interface MessageTree extends Cloneable {
	public MessageTree copy();
	public String getDomain();
	public String getHostName();
	public String getIpAddress();
	public Message getMessage();
	public String getMessageId();
	public String getParentMessageId();
	public String getRootMessageId();
	public String getSessionToken();
	public String getThreadGroupName();
	public String getThreadId();
	public String getThreadName();
	public boolean isSample();
	public void setDomain(String domain);
	public void setHostName(String hostName);
	public void setIpAddress(String ipAddress);
	public void setMessage(Message message);
	public void setMessageId(String messageId);
	public void setParentMessageId(String parentMessageId);
	public void setRootMessageId(String rootMessageId);
	public void setSessionToken(String sessionToken);
	public void setThreadGroupName(String name);
	public void setThreadId(String threadId);
	public void setThreadName(String id);
	public void setSample(boolean sample);
}

+ 7 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/codec/BufferWriter.java

@ -0,0 +1,7 @@
package com.dianping.cat.message.spi.codec;
import io.netty.buffer.ByteBuf;
public interface BufferWriter {
	public int writeTo(ByteBuf buf, byte[] data);
}

+ 42 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/codec/EscapingBufferWriter.java

@ -0,0 +1,42 @@
package com.dianping.cat.message.spi.codec;
import io.netty.buffer.ByteBuf;
public class EscapingBufferWriter implements BufferWriter {
	public static final String ID = "escape";
	@Override
	public int writeTo(ByteBuf buf, byte[] data) {
		int len = data.length;
		int count = len;
		int offset = 0;
		for (int i = 0; i < len; i++) {
			byte b = data[i];
			if (b == '\t' || b == '\r' || b == '\n' || b == '\\') {
				buf.writeBytes(data, offset, i - offset);
				buf.writeByte('\\');
				if (b == '\t') {
					buf.writeByte('t');
				} else if (b == '\r') {
					buf.writeByte('r');
				} else if (b == '\n') {
					buf.writeByte('n');
				} else {
					buf.writeByte(b);
				}
				count++;
				offset = i + 1;
			}
		}
		if (len > offset) {
			buf.writeBytes(data, offset, len - offset);
		}
		return count;
	}
}

+ 609 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/codec/PlainTextMessageCodec.java

@ -0,0 +1,609 @@
package com.dianping.cat.message.spi.codec;
import io.netty.buffer.ByteBuf;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.TimeZone;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import org.unidal.lookup.logging.LogEnabled;
import org.unidal.lookup.logging.Logger;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Metric;
import com.dianping.cat.message.Trace;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.internal.DefaultEvent;
import com.dianping.cat.message.internal.DefaultHeartbeat;
import com.dianping.cat.message.internal.DefaultMetric;
import com.dianping.cat.message.internal.DefaultTrace;
import com.dianping.cat.message.internal.DefaultTransaction;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.MessageTree;
import com.dianping.cat.message.spi.internal.DefaultMessageTree;
public class PlainTextMessageCodec implements MessageCodec, LogEnabled {
	public static final String ID = "plain-text";
	private static final String VERSION = "PT1"; // plain text version 1
	private static final byte TAB = '\t'; // tab character
	private static final byte LF = '\n'; // line feed character
	private BufferWriter m_writer = new EscapingBufferWriter();
	private BufferHelper m_bufferHelper = new BufferHelper(m_writer);
	private DateHelper m_dateHelper = new DateHelper();
	private ThreadLocal<Context> m_ctx = new ThreadLocal<Context>() {
		@Override
		protected Context initialValue() {
			return new Context();
		}
	};
	private Logger m_logger;
	@Override
	public MessageTree decode(ByteBuf buf) {
		MessageTree tree = new DefaultMessageTree();
		decode(buf, tree);
		return tree;
	}
	@Override
	public void decode(ByteBuf buf, MessageTree tree) {
		Context ctx = m_ctx.get().setBuffer(buf);
		decodeHeader(ctx, tree);
		if (buf.readableBytes() > 0) {
			decodeMessage(ctx, tree);
		}
	}
	protected void decodeHeader(Context ctx, MessageTree tree) {
		BufferHelper helper = m_bufferHelper;
		String id = helper.read(ctx, TAB);
		String domain = helper.read(ctx, TAB);
		String hostName = helper.read(ctx, TAB);
		String ipAddress = helper.read(ctx, TAB);
		String threadGroupName = helper.read(ctx, TAB);
		String threadId = helper.read(ctx, TAB);
		String threadName = helper.read(ctx, TAB);
		String messageId = helper.read(ctx, TAB);
		String parentMessageId = helper.read(ctx, TAB);
		String rootMessageId = helper.read(ctx, TAB);
		String sessionToken = helper.read(ctx, LF);
		if (VERSION.equals(id)) {
			tree.setDomain(domain);
			tree.setHostName(hostName);
			tree.setIpAddress(ipAddress);
			tree.setThreadGroupName(threadGroupName);
			tree.setThreadId(threadId);
			tree.setThreadName(threadName);
			tree.setMessageId(messageId);
			tree.setParentMessageId(parentMessageId);
			tree.setRootMessageId(rootMessageId);
			tree.setSessionToken(sessionToken);
		} else {
			throw new RuntimeException(String.format("Unrecognized id(%s) for plain text message codec!", id));
		}
	}
	protected Message decodeLine(Context ctx, DefaultTransaction parent, Stack<DefaultTransaction> stack) {
		BufferHelper helper = m_bufferHelper;
		byte identifier = ctx.getBuffer().readByte();
		String timestamp = helper.read(ctx, TAB);
		String type = helper.read(ctx, TAB);
		String name = helper.read(ctx, TAB);
		switch (identifier) {
		case 't':
			DefaultTransaction transaction = new DefaultTransaction(type, name, null);
			helper.read(ctx, LF); // get rid of line feed
			transaction.setTimestamp(m_dateHelper.parse(timestamp));
			if (parent != null) {
				parent.addChild(transaction);
			}
			stack.push(parent);
			return transaction;
		case 'A':
			DefaultTransaction tran = new DefaultTransaction(type, name, null);
			String status = helper.read(ctx, TAB);
			String duration = helper.read(ctx, TAB);
			String data = helper.read(ctx, TAB);
			helper.read(ctx, LF); // get rid of line feed
			tran.setTimestamp(m_dateHelper.parse(timestamp));
			tran.setStatus(status);
			tran.addData(data);
			long d = Long.parseLong(duration.substring(0, duration.length() - 2));
			tran.setDurationInMicros(d);
			if (parent != null) {
				parent.addChild(tran);
				return parent;
			} else {
				return tran;
			}
		case 'T':
			String transactionStatus = helper.read(ctx, TAB);
			String transactionDuration = helper.read(ctx, TAB);
			String transactionData = helper.read(ctx, TAB);
			helper.read(ctx, LF); // get rid of line feed
			parent.setStatus(transactionStatus);
			parent.addData(transactionData);
			long transactionD = Long.parseLong(transactionDuration.substring(0, transactionDuration.length() - 2));
			parent.setDurationInMicros(transactionD);
			return stack.pop();
		case 'E':
			DefaultEvent event = new DefaultEvent(type, name);
			String eventStatus = helper.read(ctx, TAB);
			String eventData = helper.read(ctx, TAB);
			helper.read(ctx, LF); // get rid of line feed
			event.setTimestamp(m_dateHelper.parse(timestamp));
			event.setStatus(eventStatus);
			event.addData(eventData);
			if (parent != null) {
				parent.addChild(event);
				return parent;
			} else {
				return event;
			}
		case 'M':
			DefaultMetric metric = new DefaultMetric(type, name);
			String metricStatus = helper.read(ctx, TAB);
			String metricData = helper.read(ctx, TAB);
			helper.read(ctx, LF); // get rid of line feed
			metric.setTimestamp(m_dateHelper.parse(timestamp));
			metric.setStatus(metricStatus);
			metric.addData(metricData);
			if (parent != null) {
				parent.addChild(metric);
				return parent;
			} else {
				return metric;
			}
		case 'L':
			DefaultTrace trace = new DefaultTrace(type, name);
			String traceStatus = helper.read(ctx, TAB);
			String traceData = helper.read(ctx, TAB);
			helper.read(ctx, LF); // get rid of line feed
			trace.setTimestamp(m_dateHelper.parse(timestamp));
			trace.setStatus(traceStatus);
			trace.addData(traceData);
			if (parent != null) {
				parent.addChild(trace);
				return parent;
			} else {
				return trace;
			}
		case 'H':
			DefaultHeartbeat heartbeat = new DefaultHeartbeat(type, name);
			String heartbeatStatus = helper.read(ctx, TAB);
			String heartbeatData = helper.read(ctx, TAB);
			helper.read(ctx, LF); // get rid of line feed
			heartbeat.setTimestamp(m_dateHelper.parse(timestamp));
			heartbeat.setStatus(heartbeatStatus);
			heartbeat.addData(heartbeatData);
			if (parent != null) {
				parent.addChild(heartbeat);
				return parent;
			} else {
				return heartbeat;
			}
		default:
			m_logger.warn("Unknown identifier(" + (char) identifier + ") of message: "
			      + ctx.getBuffer().toString(Charset.forName("utf-8")));
			throw new RuntimeException("Unknown identifier int name");
		}
	}
	protected void decodeMessage(Context ctx, MessageTree tree) {
		Stack<DefaultTransaction> stack = new Stack<DefaultTransaction>();
		Message parent = decodeLine(ctx, null, stack);
		tree.setMessage(parent);
		while (ctx.getBuffer().readableBytes() > 0) {
			Message message = decodeLine(ctx, (DefaultTransaction) parent, stack);
			if (message instanceof DefaultTransaction) {
				parent = message;
			} else {
				break;
			}
		}
	}
	@Override
	public void enableLogging(Logger logger) {
		m_logger = logger;
	}
	@Override
	public void encode(MessageTree tree, ByteBuf buf) {
		int count = 0;
		int index = buf.writerIndex();
		buf.writeInt(0); // place-holder
		count += encodeHeader(tree, buf);
		if (tree.getMessage() != null) {
			count += encodeMessage(tree.getMessage(), buf);
		}
		buf.setInt(index, count);
	}
	protected int encodeHeader(MessageTree tree, ByteBuf buf) {
		BufferHelper helper = m_bufferHelper;
		int count = 0;
		count += helper.write(buf, VERSION);
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getDomain());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getHostName());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getIpAddress());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getThreadGroupName());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getThreadId());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getThreadName());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getMessageId());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getParentMessageId());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getRootMessageId());
		count += helper.write(buf, TAB);
		count += helper.write(buf, tree.getSessionToken());
		count += helper.write(buf, LF);
		return count;
	}
	protected int encodeLine(Message message, ByteBuf buf, char type, Policy policy) {
		BufferHelper helper = m_bufferHelper;
		int count = 0;
		count += helper.write(buf, (byte) type);
		if (type == 'T' && message instanceof Transaction) {
			long duration = ((Transaction) message).getDurationInMillis();
			count += helper.write(buf, m_dateHelper.format(message.getTimestamp() + duration));
		} else {
			count += helper.write(buf, m_dateHelper.format(message.getTimestamp()));
		}
		count += helper.write(buf, TAB);
		count += helper.writeRaw(buf, message.getType());
		count += helper.write(buf, TAB);
		count += helper.writeRaw(buf, message.getName());
		count += helper.write(buf, TAB);
		if (policy != Policy.WITHOUT_STATUS) {
			count += helper.writeRaw(buf, message.getStatus());
			count += helper.write(buf, TAB);
			Object data = message.getData();
			if (policy == Policy.WITH_DURATION && message instanceof Transaction) {
				long duration = ((Transaction) message).getDurationInMicros();
				count += helper.write(buf, String.valueOf(duration));
				count += helper.write(buf, "us");
				count += helper.write(buf, TAB);
			}
			count += helper.writeRaw(buf, String.valueOf(data));
			count += helper.write(buf, TAB);
		}
		count += helper.write(buf, LF);
		return count;
	}
	public int encodeMessage(Message message, ByteBuf buf) {
		if (message instanceof Transaction) {
			Transaction transaction = (Transaction) message;
			List<Message> children = transaction.getChildren();
			if (children.isEmpty()) {
				return encodeLine(transaction, buf, 'A', Policy.WITH_DURATION);
			} else {
				int count = 0;
				int len = children.size();
				count += encodeLine(transaction, buf, 't', Policy.WITHOUT_STATUS);
				for (int i = 0; i < len; i++) {
					Message child = children.get(i);
					if (child != null) {
						count += encodeMessage(child, buf);
					}
				}
				count += encodeLine(transaction, buf, 'T', Policy.WITH_DURATION);
				return count;
			}
		} else if (message instanceof Event) {
			return encodeLine(message, buf, 'E', Policy.DEFAULT);
		} else if (message instanceof Trace) {
			return encodeLine(message, buf, 'L', Policy.DEFAULT);
		} else if (message instanceof Metric) {
			return encodeLine(message, buf, 'M', Policy.DEFAULT);
		} else if (message instanceof Heartbeat) {
			return encodeLine(message, buf, 'H', Policy.DEFAULT);
		} else {
			throw new RuntimeException(String.format("Unsupported message type: %s.", message));
		}
	}
	public void reset() {
		m_ctx.remove();
	}
	protected void setBufferWriter(BufferWriter writer) {
		m_writer = writer;
		m_bufferHelper = new BufferHelper(m_writer);
	}
	protected static class BufferHelper {
		private BufferWriter m_writer;
		public BufferHelper(BufferWriter writer) {
			m_writer = writer;
		}
		public String read(Context ctx, byte separator) {
			ByteBuf buf = ctx.getBuffer();
			char[] data = ctx.getData();
			int from = buf.readerIndex();
			int to = buf.writerIndex();
			int index = 0;
			boolean flag = false;
			for (int i = from; i < to; i++) {
				byte b = buf.readByte();
				if (b == separator) {
					break;
				}
				if (index >= data.length) {
					char[] data2 = new char[to - from];
					System.arraycopy(data, 0, data2, 0, index);
					data = data2;
				}
				char c = (char) (b & 0xFF);
				if (c > 127) {
					flag = true;
				}
				if (c == '\\' && i + 1 < to) {
					byte b2 = buf.readByte();
					if (b2 == 't') {
						c = '\t';
						i++;
					} else if (b2 == 'r') {
						c = '\r';
						i++;
					} else if (b2 == 'n') {
						c = '\n';
						i++;
					} else if (b2 == '\\') {
						c = '\\';
						i++;
					} else {
						// move back
						buf.readerIndex(i + 1);
					}
				}
				data[index] = c;
				index++;
			}
			if (!flag) {
				return new String(data, 0, index);
			} else {
				byte[] ba = new byte[index];
				for (int i = 0; i < index; i++) {
					ba[i] = (byte) (data[i] & 0xFF);
				}
				try {
					return new String(ba, 0, index, "utf-8");
				} catch (UnsupportedEncodingException e) {
					return new String(ba, 0, index);
				}
			}
		}
		public int write(ByteBuf buf, byte b) {
			buf.writeByte(b);
			return 1;
		}
		public int write(ByteBuf buf, String str) {
			if (str == null) {
				str = "null";
			}
			byte[] data = str.getBytes();
			buf.writeBytes(data);
			return data.length;
		}
		public int writeRaw(ByteBuf buf, String str) {
			if (str == null) {
				str = "null";
			}
			byte[] data;
			try {
				data = str.getBytes("utf-8");
			} catch (UnsupportedEncodingException e) {
				data = str.getBytes();
			}
			return m_writer.writeTo(buf, data);
		}
	}
	public static class Context {
		private ByteBuf m_buffer;
		private char[] m_data;
		public Context() {
			m_data = new char[4 * 1024 * 1024];
		}
		public ByteBuf getBuffer() {
			return m_buffer;
		}
		public char[] getData() {
			return m_data;
		}
		public Context setBuffer(ByteBuf buffer) {
			m_buffer = buffer;
			return this;
		}
	}
	/**
	 * Thread safe date helper class. DateFormat is NOT thread safe.
	 */
	protected static class DateHelper {
		private BlockingQueue<SimpleDateFormat> m_formats = new ArrayBlockingQueue<SimpleDateFormat>(20);
		private Map<String, Long> m_map = new ConcurrentHashMap<String, Long>();
		public String format(long timestamp) {
			SimpleDateFormat format = m_formats.poll();
			if (format == null) {
				format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
				format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
			}
			try {
				return format.format(new Date(timestamp));
			} finally {
				if (m_formats.remainingCapacity() > 0) {
					m_formats.offer(format);
				}
			}
		}
		public long parse(String str) {
			int len = str.length();
			String date = str.substring(0, 10);
			Long baseline = m_map.get(date);
			if (baseline == null) {
				try {
					SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
					format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
					baseline = format.parse(date).getTime();
					m_map.put(date, baseline);
				} catch (ParseException e) {
					return -1;
				}
			}
			long time = baseline.longValue();
			long metric = 1;
			boolean millisecond = true;
			for (int i = len - 1; i > 10; i--) {
				char ch = str.charAt(i);
				if (ch >= '0' && ch <= '9') {
					time += (ch - '0') * metric;
					metric *= 10;
				} else if (millisecond) {
					millisecond = false;
				} else {
					metric = metric / 100 * 60;
				}
			}
			return time;
		}
	}
	protected static enum Policy {
		DEFAULT,
		WITHOUT_STATUS,
		WITH_DURATION;
		public static Policy getByMessageIdentifier(byte identifier) {
			switch (identifier) {
			case 't':
				return WITHOUT_STATUS;
			case 'T':
			case 'A':
				return WITH_DURATION;
			case 'E':
			case 'H':
				return DEFAULT;
			default:
				return DEFAULT;
			}
		}
	}
}

+ 38 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/internal/DefaultMessageStatistics.java

@ -0,0 +1,38 @@
package com.dianping.cat.message.spi.internal;
import com.dianping.cat.message.spi.MessageStatistics;
import com.dianping.cat.message.spi.MessageTree;
public class DefaultMessageStatistics implements MessageStatistics {
	private long m_produced;
	private long m_overflowed;
	private long m_bytes;
	@Override
	public long getBytes() {
		return m_bytes;
	}
	@Override
	public long getOverflowed() {
		return m_overflowed;
	}
	@Override
	public long getProduced() {
		return m_produced;
	}
	@Override
	public void onBytes(int bytes) {
		m_bytes += bytes;
		m_produced++;
	}
	@Override
	public void onOverflowed(MessageTree tree) {
		m_overflowed++;
	}
}

+ 205 - 0
cat-client/src/main/java/com/dianping/cat/message/spi/internal/DefaultMessageTree.java

@ -0,0 +1,205 @@
package com.dianping.cat.message.spi.internal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.nio.charset.Charset;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.spi.MessageTree;
import com.dianping.cat.message.spi.codec.PlainTextMessageCodec;
public class DefaultMessageTree implements MessageTree {
	private ByteBuf m_buf;
	private String m_domain;
	private String m_hostName;
	private String m_ipAddress;
	private Message m_message;
	private String m_messageId;
	private String m_parentMessageId;
	private String m_rootMessageId;
	private String m_sessionToken;
	private String m_threadGroupName;
	private String m_threadId;
	private String m_threadName;
	private boolean m_sample = true;
	@Override
	public MessageTree copy() {
		MessageTree tree = new DefaultMessageTree();
		tree.setDomain(m_domain);
		tree.setHostName(m_hostName);
		tree.setIpAddress(m_ipAddress);
		tree.setMessageId(m_messageId);
		tree.setParentMessageId(m_parentMessageId);
		tree.setRootMessageId(m_rootMessageId);
		tree.setSessionToken(m_sessionToken);
		tree.setThreadGroupName(m_threadGroupName);
		tree.setThreadId(m_threadId);
		tree.setThreadName(m_threadName);
		tree.setMessage(m_message);
		tree.setSample(m_sample);
		return tree;
	}
	public ByteBuf getBuffer() {
		return m_buf;
	}
	@Override
	public String getDomain() {
		return m_domain;
	}
	@Override
	public String getHostName() {
		return m_hostName;
	}
	@Override
	public String getIpAddress() {
		return m_ipAddress;
	}
	@Override
	public Message getMessage() {
		return m_message;
	}
	@Override
	public String getMessageId() {
		return m_messageId;
	}
	@Override
	public String getParentMessageId() {
		return m_parentMessageId;
	}
	@Override
	public String getRootMessageId() {
		return m_rootMessageId;
	}
	@Override
	public String getSessionToken() {
		return m_sessionToken;
	}
	@Override
	public String getThreadGroupName() {
		return m_threadGroupName;
	}
	@Override
	public String getThreadId() {
		return m_threadId;
	}
	@Override
	public String getThreadName() {
		return m_threadName;
	}
	@Override
	public boolean isSample() {
		return m_sample;
	}
	public void setBuffer(ByteBuf buf) {
		m_buf = buf;
	}
	@Override
	public void setDomain(String domain) {
		m_domain = domain;
	}
	@Override
	public void setHostName(String hostName) {
		m_hostName = hostName;
	}
	@Override
	public void setIpAddress(String ipAddress) {
		m_ipAddress = ipAddress;
	}
	@Override
	public void setMessage(Message message) {
		m_message = message;
	}
	@Override
	public void setMessageId(String messageId) {
		if (messageId != null && messageId.length() > 0) {
			m_messageId = messageId;
		}
	}
	@Override
	public void setParentMessageId(String parentMessageId) {
		if (parentMessageId != null && parentMessageId.length() > 0) {
			m_parentMessageId = parentMessageId;
		}
	}
	@Override
	public void setRootMessageId(String rootMessageId) {
		if (rootMessageId != null && rootMessageId.length() > 0) {
			m_rootMessageId = rootMessageId;
		}
	}
	@Override
	public void setSample(boolean sample) {
		m_sample = sample;
	}
	@Override
	public void setSessionToken(String sessionToken) {
		m_sessionToken = sessionToken;
	}
	@Override
	public void setThreadGroupName(String threadGroupName) {
		m_threadGroupName = threadGroupName;
	}
	@Override
	public void setThreadId(String threadId) {
		m_threadId = threadId;
	}
	@Override
	public void setThreadName(String threadName) {
		m_threadName = threadName;
	}
	@Override
	public String toString() {
		PlainTextMessageCodec codec = new PlainTextMessageCodec();
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
		codec.encode(this, buf);
		buf.readInt(); // get rid of length
		codec.reset();
		return buf.toString(Charset.forName("utf-8"));
	}
}

+ 349 - 0
cat-client/src/main/java/com/dianping/cat/servlet/CatFilter.java

@ -0,0 +1,349 @@
package com.dianping.cat.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.unidal.helper.Joiners;
import org.unidal.helper.Joiners.IBuilder;
import com.dianping.cat.Cat;
import com.dianping.cat.CatConstants;
import com.dianping.cat.configuration.client.entity.Server;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.internal.DefaultMessageManager;
import com.dianping.cat.message.internal.DefaultTransaction;
public class CatFilter implements Filter {
	private List<Handler> m_handlers = new ArrayList<Handler>();
	@Override
	public void destroy() {
	}
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
	      ServletException {
		Context ctx = new Context((HttpServletRequest) request, (HttpServletResponse) response, chain, m_handlers);
		ctx.handle();
	}
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		m_handlers.add(CatHandler.ENVIRONMENT);
		m_handlers.add(CatHandler.LOG_SPAN);
		m_handlers.add(CatHandler.LOG_CLIENT_PAYLOAD);
		m_handlers.add(CatHandler.ID_SETUP);
	}
	private static enum CatHandler implements Handler {
		ENVIRONMENT {
			@Override
			public void handle(Context ctx) throws IOException, ServletException {
				HttpServletRequest req = ctx.getRequest();
				boolean top = !Cat.getManager().hasContext();
				ctx.setTop(top);
				if (top) {
					ctx.setType(CatConstants.TYPE_URL);
					setTraceMode(req);
				} else {
					ctx.setType(CatConstants.TYPE_URL_FORWARD);
				}
				ctx.handle();
			}
			protected void setTraceMode(HttpServletRequest req) {
				String traceMode = "X-CAT-TRACE-MODE";
				String headMode = req.getHeader(traceMode);
				if ("true".equals(headMode)) {
					Cat.getManager().setTraceMode(true);
				}
			}
		},
		ID_SETUP {
			private String m_servers;
			private String getCatServer() {
				if (m_servers == null) {
					DefaultMessageManager manager = (DefaultMessageManager) Cat.getManager();
					List<Server> servers = manager.getConfigManager().getServers();
					m_servers = Joiners.by(',').join(servers, new IBuilder<Server>() {
						@Override
						public String asString(Server server) {
							String ip = server.getIp();
							Integer httpPort = server.getHttpPort();
							return ip + ":" + httpPort;
						}
					});
				}
				return m_servers;
			}
			@Override
			public void handle(Context ctx) throws IOException, ServletException {
				boolean isTraceMode = Cat.getManager().isTraceMode();
				HttpServletResponse res = ctx.getResponse();
				if (isTraceMode) {
					String id = Cat.getCurrentMessageId();
					res.setHeader("X-CAT-ROOT-ID", id);
					res.setHeader("X-CAT-SERVER", getCatServer());
				}
				ctx.handle();
			}
		},
		LOG_CLIENT_PAYLOAD {
			@Override
			public void handle(Context ctx) throws IOException, ServletException {
				HttpServletRequest req = ctx.getRequest();
				String type = ctx.getType();
				if (ctx.isTop()) {
					logRequestClientInfo(req, type);
					logRequestPayload(req, type);
				} else {
					logRequestPayload(req, type);
				}
				ctx.handle();
			}
			protected void logRequestClientInfo(HttpServletRequest req, String type) {
				StringBuilder sb = new StringBuilder(1024);
				String ip = "";
				String ipForwarded = req.getHeader("x-forwarded-for");
				if (ipForwarded == null) {
					ip = req.getRemoteAddr();
				} else {
					ip = ipForwarded;
				}
				sb.append("IPS=").append(ip);
				sb.append("&VirtualIP=").append(req.getRemoteAddr());
				sb.append("&Server=").append(req.getServerName());
				sb.append("&Referer=").append(req.getHeader("referer"));
				sb.append("&Agent=").append(req.getHeader("user-agent"));
				Cat.logEvent(type, type + ".Server", Message.SUCCESS, sb.toString());
			}
			protected void logRequestPayload(HttpServletRequest req, String type) {
				StringBuilder sb = new StringBuilder(256);
				sb.append(req.getScheme().toUpperCase()).append('/');
				sb.append(req.getMethod()).append(' ').append(req.getRequestURI());
				String qs = req.getQueryString();
				if (qs != null) {
					sb.append('?').append(qs);
				}
				Cat.logEvent(type, type + ".Method", Message.SUCCESS, sb.toString());
			}
		},
		LOG_SPAN {
			public static final char SPLIT = '/';
			private void customizeStatus(Transaction t, HttpServletRequest req) {
				Object catStatus = req.getAttribute(CatConstants.CAT_STATE);
				if (catStatus != null) {
					t.setStatus(catStatus.toString());
				} else {
					t.setStatus(Message.SUCCESS);
				}
			}
			private void customizeUri(Transaction t, HttpServletRequest req) {
				if (t instanceof DefaultTransaction) {
					Object catPageType = req.getAttribute(CatConstants.CAT_PAGE_TYPE);
					if (catPageType instanceof String) {
						((DefaultTransaction) t).setType(catPageType.toString());
					}
					Object catPageUri = req.getAttribute(CatConstants.CAT_PAGE_URI);
					if (catPageUri instanceof String) {
						((DefaultTransaction) t).setName(catPageUri.toString());
					}
				}
			}
			private String getRequestURI(HttpServletRequest req) {
				String url = req.getRequestURI();
				int length = url.length();
				StringBuilder sb = new StringBuilder(length);
				for (int index = 0; index < length;) {
					char c = url.charAt(index);
					if (c == SPLIT && index < length - 1) {
						sb.append(c);
						StringBuilder nextSection = new StringBuilder();
						boolean isNumber = false;
						boolean first = true;
						for (int j = index + 1; j < length; j++) {
							char next = url.charAt(j);
							if ((first || isNumber == true) && next != SPLIT) {
								isNumber = isNumber(next);
								first = false;
							}
							if (next == SPLIT) {
								if (isNumber) {
									sb.append("{num}");
								} else {
									sb.append(nextSection.toString());
								}
								index = j;
								break;
							} else if (j == length - 1) {
								if (isNumber) {
									sb.append("{num}");
								} else {
									nextSection.append(next);
									sb.append(nextSection.toString());
								}
								index = j + 1;
								break;
							} else {
								nextSection.append(next);
							}
						}
					} else {
						sb.append(c);
						index++;
					}
				}
				return sb.toString();
			}
			@Override
			public void handle(Context ctx) throws IOException, ServletException {
				HttpServletRequest req = ctx.getRequest();
				Transaction t = Cat.newTransaction(ctx.getType(), getRequestURI(req));
				try {
					ctx.handle();
					customizeStatus(t, req);
				} catch (ServletException e) {
					t.setStatus(e);
					Cat.logError(e);
					throw e;
				} catch (IOException e) {
					t.setStatus(e);
					Cat.logError(e);
					throw e;
				} catch (Throwable e) {
					t.setStatus(e);
					Cat.logError(e);
					throw new RuntimeException(e);
				} finally {
					customizeUri(t, req);
					t.complete();
				}
			}
			private boolean isNumber(char c) {
				return (c >= '0' && c <= '9') || c == '.' || c == '-' || c == ',';
			}
		};
	}
	protected static class Context {
		private FilterChain m_chain;
		private List<Handler> m_handlers;
		private int m_index;
		private HttpServletRequest m_request;
		private HttpServletResponse m_response;
		private boolean m_top;
		private String m_type;
		public Context(HttpServletRequest request, HttpServletResponse response, FilterChain chain, List<Handler> handlers) {
			m_request = request;
			m_response = response;
			m_chain = chain;
			m_handlers = handlers;
		}
		public HttpServletRequest getRequest() {
			return m_request;
		}
		public HttpServletResponse getResponse() {
			return m_response;
		}
		public String getType() {
			return m_type;
		}
		public void handle() throws IOException, ServletException {
			if (m_index < m_handlers.size()) {
				Handler handler = m_handlers.get(m_index++);
				handler.handle(this);
			} else {
				m_chain.doFilter(m_request, m_response);
			}
		}
		public boolean isTop() {
			return m_top;
		}
		public void setTop(boolean top) {
			m_top = top;
		}
		public void setType(String type) {
			m_type = type;
		}
	}
	protected static interface Handler {
		public void handle(Context ctx) throws IOException, ServletException;
	}
}

+ 37 - 0
cat-client/src/main/java/com/dianping/cat/status/HeartbeatExtenstion.java

@ -0,0 +1,37 @@
package com.dianping.cat.status;
import java.util.HashMap;
import java.util.Map;
import org.unidal.lookup.extension.Initializable;
import org.unidal.lookup.extension.InitializationException;
public class HeartbeatExtenstion implements StatusExtension, Initializable {
	@Override
	public String getId() {
		return "MyTestId";
	}
	@Override
	public String getDescription() {
		return "MyDescription";
	}
	@Override
	public Map<String, String> getProperties() {
		Map<String, String> maps = new HashMap<String, String>();
		maps.put("key1", String.valueOf(1));
		maps.put("key2", String.valueOf(2));
		maps.put("key3", String.valueOf(3));
		return maps;
	}
	@Override
	public void initialize() throws InitializationException {
		StatusExtensionRegister.getInstance().register(this);
	}
}

+ 12 - 0
cat-client/src/main/java/com/dianping/cat/status/StatusExtension.java

@ -0,0 +1,12 @@
package com.dianping.cat.status;
import java.util.Map;
public interface StatusExtension {
	public String getId();
	public String getDescription();
	public Map<String, String> getProperties();
}

+ 36 - 0
cat-client/src/main/java/com/dianping/cat/status/StatusExtensionRegister.java

@ -0,0 +1,36 @@
package com.dianping.cat.status;
import java.util.ArrayList;
import java.util.List;
public class StatusExtensionRegister {
	public static StatusExtensionRegister getInstance() {
		return s_register;
	}
	private List<StatusExtension> m_extensions = new ArrayList<StatusExtension>();
	public static StatusExtensionRegister s_register = new StatusExtensionRegister();
	private StatusExtensionRegister() {
	}
	public List<StatusExtension> getStatusExtension() {
		synchronized (this) {
			return m_extensions;
		}
	}
	public void register(StatusExtension monitor) {
		synchronized (this) {
			m_extensions.add(monitor);
		}
	}
	public void unregister(StatusExtension monitor) {
		synchronized (this) {
			m_extensions.remove(monitor);
		}
	}
}

+ 287 - 0
cat-client/src/main/java/com/dianping/cat/status/StatusInfoCollector.java

@ -0,0 +1,287 @@
package com.dianping.cat.status;
import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;
import com.dianping.cat.message.spi.MessageStatistics;
import com.dianping.cat.status.model.entity.DiskInfo;
import com.dianping.cat.status.model.entity.DiskVolumeInfo;
import com.dianping.cat.status.model.entity.Extension;
import com.dianping.cat.status.model.entity.GcInfo;
import com.dianping.cat.status.model.entity.MemoryInfo;
import com.dianping.cat.status.model.entity.MessageInfo;
import com.dianping.cat.status.model.entity.OsInfo;
import com.dianping.cat.status.model.entity.RuntimeInfo;
import com.dianping.cat.status.model.entity.StatusInfo;
import com.dianping.cat.status.model.entity.ThreadsInfo;
import com.dianping.cat.status.model.transform.BaseVisitor;
public class StatusInfoCollector extends BaseVisitor {
	private MessageStatistics m_statistics;
	private boolean m_dumpLocked;
	private String m_jars;
	private String m_dataPath = "/data";
	private StatusInfo m_statusInfo;
	public StatusInfoCollector(MessageStatistics statistics, String jars) {
		m_statistics = statistics;
		m_jars = jars;
	}
	private int countThreadsByPrefix(ThreadInfo[] threads, String... prefixes) {
		int count = 0;
		for (ThreadInfo thread : threads) {
			for (String prefix : prefixes) {
				if (thread.getThreadName().startsWith(prefix)) {
					count++;
				}
			}
		}
		return count;
	}
	private int countThreadsBySubstring(ThreadInfo[] threads, String... substrings) {
		int count = 0;
		for (ThreadInfo thread : threads) {
			for (String str : substrings) {
				if (thread.getThreadName().contains(str)) {
					count++;
				}
			}
		}
		return count;
	}
	private String getThreadDump(ThreadInfo[] threads) {
		StringBuilder sb = new StringBuilder(32768);
		int index = 1;
		TreeMap<String, ThreadInfo> sortedThreads = new TreeMap<String, ThreadInfo>();
		for (ThreadInfo thread : threads) {
			sortedThreads.put(thread.getThreadName(), thread);
		}
		for (ThreadInfo thread : sortedThreads.values()) {
			sb.append(index++).append(": ").append(thread);
		}
		return sb.toString();
	}
	boolean isInstanceOfInterface(Class<?> clazz, String interfaceName) {
		if (clazz == Object.class) {
			return false;
		} else if (clazz.getName().equals(interfaceName)) {
			return true;
		}
		Class<?>[] interfaceclasses = clazz.getInterfaces();
		for (Class<?> interfaceClass : interfaceclasses) {
			if (isInstanceOfInterface(interfaceClass, interfaceName)) {
				return true;
			}
		}
		return isInstanceOfInterface(clazz.getSuperclass(), interfaceName);
	}
	public StatusInfoCollector setDumpLocked(boolean dumpLocked) {
		m_dumpLocked = dumpLocked;
		return this;
	}
	@Override
	public void visitDisk(DiskInfo disk) {
		File[] roots = File.listRoots();
		if (roots != null) {
			for (File root : roots) {
				disk.addDiskVolume(new DiskVolumeInfo(root.getAbsolutePath()));
			}
		}
		File data = new File(m_dataPath);
		if (data.exists()) {
			disk.addDiskVolume(new DiskVolumeInfo(data.getAbsolutePath()));
		}
		super.visitDisk(disk);
	}
	@Override
	public void visitDiskVolume(DiskVolumeInfo diskVolume) {
		Extension diskExtension = m_statusInfo.findOrCreateExtension("Disk");
		File volume = new File(diskVolume.getId());
		diskVolume.setTotal(volume.getTotalSpace());
		diskVolume.setFree(volume.getFreeSpace());
		diskVolume.setUsable(volume.getUsableSpace());
		diskExtension.findOrCreateExtensionDetail(diskVolume.getId() + " Free").setValue(volume.getFreeSpace());
	}
	@Override
	public void visitMemory(MemoryInfo memory) {
		MemoryMXBean bean = ManagementFactory.getMemoryMXBean();
		Runtime runtime = Runtime.getRuntime();
		memory.setMax(runtime.maxMemory());
		memory.setTotal(runtime.totalMemory());
		memory.setFree(runtime.freeMemory());
		memory.setHeapUsage(bean.getHeapMemoryUsage().getUsed());
		memory.setNonHeapUsage(bean.getNonHeapMemoryUsage().getUsed());
		List<GarbageCollectorMXBean> beans = ManagementFactory.getGarbageCollectorMXBeans();
		Extension gcExtension = m_statusInfo.findOrCreateExtension("GC");
		for (GarbageCollectorMXBean mxbean : beans) {
			if (mxbean.isValid()) {
				GcInfo gc = new GcInfo();
				String name = mxbean.getName();
				long count = mxbean.getCollectionCount();
				gc.setName(name);
				gc.setCount(count);
				gc.setTime(mxbean.getCollectionTime());
				memory.addGc(gc);
				gcExtension.findOrCreateExtensionDetail(name + "Count").setValue(count);
				gcExtension.findOrCreateExtensionDetail(name + "Time").setValue(mxbean.getCollectionTime());
			}
		}
		Extension heapUsage = m_statusInfo.findOrCreateExtension("JVMHeap");
		for (MemoryPoolMXBean mpBean : ManagementFactory.getMemoryPoolMXBeans()) {
			long count = mpBean.getUsage().getUsed();
			String name = mpBean.getName();
			heapUsage.findOrCreateExtensionDetail(name).setValue(count);
		}
		super.visitMemory(memory);
	}
	@Override
	public void visitMessage(MessageInfo message) {
		Extension catExtension = m_statusInfo.findOrCreateExtension("CatUsage");
		if (m_statistics != null) {
			catExtension.findOrCreateExtensionDetail("Produced").setValue(m_statistics.getProduced());
			catExtension.findOrCreateExtensionDetail("Overflowed").setValue(m_statistics.getOverflowed());
			catExtension.findOrCreateExtensionDetail("Bytes").setValue(m_statistics.getBytes());
		}
	}
	@Override
	public void visitOs(OsInfo os) {
		Extension systemExtension = m_statusInfo.findOrCreateExtension("System");
		OperatingSystemMXBean bean = ManagementFactory.getOperatingSystemMXBean();
		os.setArch(bean.getArch());
		os.setName(bean.getName());
		os.setVersion(bean.getVersion());
		os.setAvailableProcessors(bean.getAvailableProcessors());
		os.setSystemLoadAverage(bean.getSystemLoadAverage());
		systemExtension.findOrCreateExtensionDetail("LoadAverage").setValue(bean.getSystemLoadAverage());
		// for Sun JDK
		if (isInstanceOfInterface(bean.getClass(), "com.sun.management.OperatingSystemMXBean")) {
			com.sun.management.OperatingSystemMXBean b = (com.sun.management.OperatingSystemMXBean) bean;
			os.setTotalPhysicalMemory(b.getTotalPhysicalMemorySize());
			os.setFreePhysicalMemory(b.getFreePhysicalMemorySize());
			os.setTotalSwapSpace(b.getTotalSwapSpaceSize());
			os.setFreeSwapSpace(b.getFreeSwapSpaceSize());
			os.setProcessTime(b.getProcessCpuTime());
			os.setCommittedVirtualMemory(b.getCommittedVirtualMemorySize());
			systemExtension.findOrCreateExtensionDetail("FreePhysicalMemory").setValue(b.getFreePhysicalMemorySize());
			systemExtension.findOrCreateExtensionDetail("FreeSwapSpaceSize").setValue(b.getFreeSwapSpaceSize());
		}
		m_statusInfo.addExtension(systemExtension);
	}
	@Override
	public void visitRuntime(RuntimeInfo runtime) {
		RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
		runtime.setStartTime(bean.getStartTime());
		runtime.setUpTime(bean.getUptime());
		runtime.setJavaClasspath(m_jars);
		runtime.setJavaVersion(System.getProperty("java.version"));
		runtime.setUserDir(System.getProperty("user.dir"));
		runtime.setUserName(System.getProperty("user.name"));
	}
	@Override
	public void visitStatus(StatusInfo status) {
		status.setTimestamp(new Date());
		status.setOs(new OsInfo());
		status.setDisk(new DiskInfo());
		status.setRuntime(new RuntimeInfo());
		status.setMemory(new MemoryInfo());
		status.setThread(new ThreadsInfo());
		status.setMessage(new MessageInfo());
		m_statusInfo = status;
		super.visitStatus(status);
	}
	@Override
	public void visitThread(ThreadsInfo thread) {
		Extension frameworkThread = m_statusInfo.findOrCreateExtension("FrameworkThread");
		ThreadMXBean bean = ManagementFactory.getThreadMXBean();
		bean.setThreadContentionMonitoringEnabled(true);
		ThreadInfo[] threads;
		if (m_dumpLocked) {
			threads = bean.dumpAllThreads(true, true);
		} else {
			threads = bean.dumpAllThreads(false, false);
		}
		thread.setCount(bean.getThreadCount());
		thread.setDaemonCount(bean.getDaemonThreadCount());
		thread.setPeekCount(bean.getPeakThreadCount());
		thread.setTotalStartedCount((int) bean.getTotalStartedThreadCount());
		int jbossThreadsCount = countThreadsByPrefix(threads, "http-", "catalina-exec-");
		int jettyThreadsCount = countThreadsBySubstring(threads, "@qtp");
		thread.setDump(getThreadDump(threads));
		frameworkThread.findOrCreateExtensionDetail("HttpThread").setValue(jbossThreadsCount + jettyThreadsCount);
		frameworkThread.findOrCreateExtensionDetail("CatThread").setValue(countThreadsByPrefix(threads, "Cat-"));
		frameworkThread.findOrCreateExtensionDetail("PigeonThread").setValue(
		      countThreadsByPrefix(threads, "Pigeon-", "DPSF-", "Netty-", "Client-ResponseProcessor"));
		frameworkThread.findOrCreateExtensionDetail("ActiveThread").setValue(bean.getThreadCount());
		frameworkThread.findOrCreateExtensionDetail("StartedThread").setValue(bean.getTotalStartedThreadCount());
		m_statusInfo.addExtension(frameworkThread);
	}
}

+ 193 - 0
cat-client/src/main/java/com/dianping/cat/status/StatusUpdateTask.java

@ -0,0 +1,193 @@
package com.dianping.cat.status;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.unidal.helper.Threads.Task;
import org.unidal.lookup.annotation.Inject;
import org.unidal.lookup.extension.Initializable;
import org.unidal.lookup.extension.InitializationException;
import com.dianping.cat.Cat;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.configuration.NetworkInterfaceManager;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.MessageProducer;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.internal.MilliSecondTimer;
import com.dianping.cat.message.spi.MessageStatistics;
import com.dianping.cat.status.model.entity.Extension;
import com.dianping.cat.status.model.entity.StatusInfo;
public class StatusUpdateTask implements Task, Initializable {
	@Inject
	private MessageStatistics m_statistics;
	@Inject
	private ClientConfigManager m_manager;
	private boolean m_active = true;
	private String m_ipAddress;
	private long m_interval = 60 * 1000; // 60 seconds
	private String m_jars;
	private void buildClasspath() {
		ClassLoader loader = StatusUpdateTask.class.getClassLoader();
		StringBuilder sb = new StringBuilder();
		buildClasspath(loader, sb);
		if (sb.length() > 0) {
			m_jars = sb.substring(0, sb.length() - 1);
		}
	}
	private void buildClasspath(ClassLoader loader, StringBuilder sb) {
		if (loader instanceof URLClassLoader) {
			URL[] urLs = ((URLClassLoader) loader).getURLs();
			for (URL url : urLs) {
				String jar = parseJar(url.toExternalForm());
				if (jar != null) {
					sb.append(jar).append(',');
				}
			}
			ClassLoader parent = loader.getParent();
			buildClasspath(parent, sb);
		}
	}
	private void buildExtensionData(StatusInfo status) {
		StatusExtensionRegister res = StatusExtensionRegister.getInstance();
		List<StatusExtension> extensions = res.getStatusExtension();
		for (StatusExtension extension : extensions) {
			String id = extension.getId();
			String des = extension.getDescription();
			Map<String, String> propertis = extension.getProperties();
			Extension item = status.findOrCreateExtension(id).setDescription(des);
			for (Entry<String, String> entry : propertis.entrySet()) {
				try {
					double value = Double.parseDouble(entry.getValue());
					item.findOrCreateExtensionDetail(entry.getKey()).setValue(value);
				} catch (Exception e) {
					Cat.logError("StatusExtension can only be double type", e);
				}
			}
		}
	}
	@Override
	public String getName() {
		return "StatusUpdateTask";
	}
	@Override
	public void initialize() throws InitializationException {
		m_ipAddress = NetworkInterfaceManager.INSTANCE.getLocalHostAddress();
	}
	private String parseJar(String path) {
		if (path.endsWith(".jar")) {
			int index = path.lastIndexOf('/');
			if (index > -1) {
				return path.substring(index + 1);
			}
		}
		return null;
	}
	@Override
	public void run() {
		// try to wait cat client init success
		try {
			Thread.sleep(10 * 1000);
		} catch (InterruptedException e) {
			return;
		}
		while (true) {
			Calendar cal = Calendar.getInstance();
			int second = cal.get(Calendar.SECOND);
			// try to avoid send heartbeat at 59-01 second
			if (second < 2 || second > 58) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// ignore it
				}
			} else {
				break;
			}
		}
		try {
			buildClasspath();
		} catch (Exception e) {
			e.printStackTrace();
		}
		MessageProducer cat = Cat.getProducer();
		Transaction reboot = cat.newTransaction("System", "Reboot");
		reboot.setStatus(Message.SUCCESS);
		cat.logEvent("Reboot", NetworkInterfaceManager.INSTANCE.getLocalHostAddress(), Message.SUCCESS, null);
		reboot.complete();
		while (m_active) {
			long start = MilliSecondTimer.currentTimeMillis();
			if (m_manager.isCatEnabled()) {
				Transaction t = cat.newTransaction("System", "Status");
				Heartbeat h = cat.newHeartbeat("Heartbeat", m_ipAddress);
				StatusInfo status = new StatusInfo();
				t.addData("dumpLocked", m_manager.isDumpLocked());
				try {
					StatusInfoCollector statusInfoCollector = new StatusInfoCollector(m_statistics, m_jars);
					status.accept(statusInfoCollector.setDumpLocked(m_manager.isDumpLocked()));
					buildExtensionData(status);
					h.addData(status.toString());
					h.setStatus(Message.SUCCESS);
				} catch (Throwable e) {
					h.setStatus(e);
					cat.logError(e);
				} finally {
					h.complete();
				}
				t.setStatus(Message.SUCCESS);
				t.complete();
			}
			long elapsed = MilliSecondTimer.currentTimeMillis() - start;
			if (elapsed < m_interval) {
				try {
					Thread.sleep(m_interval - elapsed);
				} catch (InterruptedException e) {
					break;
				}
			}
		}
	}
	public void setInterval(long interval) {
		m_interval = interval;
	}
	@Override
	public void shutdown() {
		m_active = false;
	}
}

+ 30 - 0
cat-client/src/main/java/com/dianping/cat/utils/HttpRequestUtils.java

@ -0,0 +1,30 @@
package com.dianping.cat.utils;
import javax.servlet.http.HttpServletRequest;
/**
 * 获取客户端实际ip
 */
public class HttpRequestUtils {
    public static String getAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Real-IP");
        if (ip != null && ip.length() != 0 && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        ip = request.getHeader("X-Forwarded-For");
        if (ip != null && ip.length() != 0 && !"unKnown".equalsIgnoreCase(ip)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        return request.getRemoteAddr();
    }
}

+ 77 - 0
cat-client/src/main/java/com/site/helper/JsonBuilder.java

@ -0,0 +1,77 @@
package com.site.helper;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class JsonBuilder {
	private FieldNamingStrategy m_fieldNamingStrategy = new FieldNamingStrategy() {
		@Override
		public String translateName(Field f) {
			String name = f.getName();
			if (name.startsWith("m_")) {
				return name.substring(2);
			} else {
				return name;
			}
		}
	};
	private Gson m_gson = new GsonBuilder().registerTypeAdapter(Timestamp.class, new TimestampTypeAdapter())
	      .setDateFormat("yyyy-MM-dd HH:mm:ss").setFieldNamingStrategy(m_fieldNamingStrategy).create();
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object parse(String json, Class clz) {
		return m_gson.fromJson(json, clz);
	}
	public String toJson(Object o) {
		return m_gson.toJson(o);
	}
	public String toJsonWithEnter(Object o) {
		return m_gson.toJson(o) + "\n";
	}
	public class TimestampTypeAdapter implements JsonSerializer<Timestamp>, JsonDeserializer<Timestamp> {
		private final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
		      throws JsonParseException {
			if (!(json instanceof JsonPrimitive)) {
				throw new JsonParseException("The date should be a string value");
			}
			try {
				Date date = format.parse(json.getAsString());
				return new Timestamp(date.getTime());
			} catch (ParseException e) {
				throw new JsonParseException(e);
			}
		}
		public JsonElement serialize(Timestamp src, Type arg1, JsonSerializationContext arg2) {
			String dateFormatAsString = format.format(new Date(src.getTime()));
			return new JsonPrimitive(dateFormatAsString);
		}
	}
}

+ 124 - 0
cat-client/src/main/java/com/site/helper/Splitters.java

@ -0,0 +1,124 @@
package com.site.helper;
import java.util.ArrayList;
import java.util.List;
public class Splitters {
   public static StringSplitter by(char delimiter) {
      return new StringSplitter(delimiter);
   }
   public static StringSplitter by(String delimiter) {
      return new StringSplitter(delimiter);
   }
   public static class StringSplitter {
      private char m_charDelimiter;
      private String m_stringDelimiter;
      private boolean m_trimmed;
      private boolean m_noEmptyItem;
      StringSplitter(char delimiter) {
         m_charDelimiter = delimiter;
      }
      StringSplitter(String delimiter) {
         m_stringDelimiter = delimiter;
      }
      protected List<String> doCharSplit(String str, List<String> list) {
         char delimiter = m_charDelimiter;
         int len = str.length();
         StringBuilder sb = new StringBuilder(len);
         for (int i = 0; i < len + 1; i++) {
            char ch = i == len ? delimiter : str.charAt(i);
            if (ch == delimiter) {
               String item = sb.toString();
               sb.setLength(0);
               if (m_trimmed) {
                  item = item.trim();
               }
               if (m_noEmptyItem && item.length() == 0) {
                  continue;
               }
               list.add(item);
            } else {
               sb.append(ch);
            }
         }
         return list;
      }
      protected List<String> doStringSplit(String source, List<String> list) {
         String delimiter = m_stringDelimiter;
         int len = delimiter.length();
         int offset = 0;
         int index = source.indexOf(delimiter, offset);
         while (true) {
            String part;
            if (index == -1) { // last part
               part = source.substring(offset);
            } else {
               part = source.substring(offset, index);
            }
            if (m_trimmed) {
               part = part.trim();
            }
            if (!m_noEmptyItem || part.length() > 0) {
               list.add(part);
            }
            if (index == -1) { // last part
               break;
            } else {
               offset = index + len;
               index = source.indexOf(delimiter, offset);
            }
         }
         return list;
      }
      public StringSplitter noEmptyItem() {
         m_noEmptyItem = true;
         return this;
      }
      public List<String> split(String str) {
         return split(str, new ArrayList<String>());
      }
      public List<String> split(String str, List<String> list) {
         if (str == null) {
            return null;
         }
         if (m_charDelimiter > 0) {
            return doCharSplit(str, list);
         } else if (m_stringDelimiter != null) {
            return doStringSplit(str, list);
         }
         throw new UnsupportedOperationException();
      }
      public StringSplitter trim() {
         m_trimmed = true;
         return this;
      }
   }
}

+ 309 - 0
cat-client/src/main/java/com/site/helper/Stringizers.java

@ -0,0 +1,309 @@
package com.site.helper;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.unidal.helper.Reflects;
import org.unidal.helper.Reflects.IMemberFilter;
public class Stringizers {
   public static JsonStringizer forJson() {
      return JsonStringizer.DEFAULT;
   }
   public static enum JsonStringizer {
      DEFAULT(false),
      COMPACT(true);
      private boolean m_compact;
      private JsonStringizer(boolean compact) {
         m_compact = compact;
      }
      public JsonStringizer compact() {
         return COMPACT;
      }
      public String from(Object obj) {
         return from(obj, 0, 0);
      }
      public String from(Object obj, int maxLength, int maxItemLength) {
         StringBuilder sb = new StringBuilder(1024);
         LengthLimiter limiter = new LengthLimiter(sb, maxLength, maxItemLength);
         Set<Object> done = new HashSet<Object>();
         try {
            fromObject(done, limiter, obj);
         } catch (RuntimeException e) {
            // expected
            sb.append("...");
         }
         return sb.toString();
      }
      private void fromArray(Set<Object> done, LengthLimiter sb, Object obj) {
         int len = Array.getLength(obj);
         sb.append('[');
         for (int i = 0; i < len; i++) {
            if (i > 0) {
               sb.append(',');
               if (!m_compact) {
                  sb.append(' ');
               }
            }
            Object element = Array.get(obj, i);
            fromObject(done, sb, element);
         }
         sb.append(']');
      }
      @SuppressWarnings("unchecked")
      private void fromCollection(Set<Object> done, LengthLimiter sb, Object obj) {
         boolean first = true;
         sb.append('[');
         for (Object item : ((Collection<Object>) obj)) {
            if (first) {
               first = false;
            } else {
               sb.append(',');
               if (!m_compact) {
                  sb.append(' ');
               }
            }
            fromObject(done, sb, item);
         }
         sb.append(']');
      }
      @SuppressWarnings("unchecked")
      private void fromMap(Set<Object> done, LengthLimiter sb, Object obj) {
         boolean first = true;
         sb.append('{');
         for (Map.Entry<Object, Object> e : ((Map<Object, Object>) obj).entrySet()) {
            Object key = e.getKey();
            Object value = e.getValue();
            if (value == null) {
               continue;
            }
            if (first) {
               first = false;
            } else {
               sb.append(',');
               if (!m_compact) {
                  sb.append(' ');
               }
            }
            sb.append('"').append(key).append("\":");
            if (!m_compact) {
               sb.append(' ');
            }
            fromObject(done, sb, value);
         }
         sb.append('}');
      }
      private void fromObject(Set<Object> done, LengthLimiter sb, Object obj) {
         if (obj == null) {
            return;
         }
         Class<?> type = obj.getClass();
         if (type == String.class) {
            sb.append('"').append(obj.toString(), true).append('"');
         } else if (type.isPrimitive() || Number.class.isAssignableFrom(type) || type.isEnum()) {
            sb.append(obj.toString(), true);
         } else if (type == Boolean.class) {
            sb.append(obj.toString(), true);
         } else if (type == Date.class) {
            sb.append('"').append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(obj), true).append('"');
         } else if (type == Class.class) {
            sb.append('"').append(obj, true).append('"');
         } else if (done.contains(obj)) {
            sb.append("{}");
            return;
         } else {
            done.add(obj);
            if (type.isArray()) {
               fromArray(done, sb, obj);
            } else if (Collection.class.isAssignableFrom(type)) {
               fromCollection(done, sb, obj);
            } else if (Map.class.isAssignableFrom(type)) {
               fromMap(done, sb, obj);
            } else {
               fromPojo(done, sb, obj);
            }
         }
      }
      private void fromPojo(Set<Object> done, LengthLimiter sb, Object obj) {
         Class<? extends Object> type = obj.getClass();
         if (hasToString(type)) {
            fromObject(done, sb, obj.toString());
            return;
         }
         List<Method> getters = Reflects.forMethod().getMethods(type, new IMemberFilter<Method>() {
            @Override
            public boolean filter(Method method) {
               return Reflects.forMethod().isGetter(method);
            }
         });
         Collections.sort(getters, new Comparator<Method>() {
            @Override
            public int compare(Method m1, Method m2) {
               return m1.getName().compareTo(m2.getName());
            }
         });
         if (getters.isEmpty()) {
            // use java toString() since we can't handle it
            sb.append(obj.toString());
         } else {
            boolean first = true;
            sb.append('{');
            for (Method getter : getters) {
               String key = Reflects.forMethod().getGetterName(getter);
               Object value;
               try {
                  if (!getter.isAccessible()) {
                     getter.setAccessible(true);
                  }
                  value = getter.invoke(obj);
               } catch (Exception e) {
                  // ignore it
                  value = null;
               }
               if (value == null) {
                  continue;
               }
               if (first) {
                  first = false;
               } else {
                  sb.append(',');
                  if (!m_compact) {
                     sb.append(' ');
                  }
               }
               sb.append('"').append(key).append("\":");
               if (!m_compact) {
                  sb.append(' ');
               }
               fromObject(done, sb, value);
            }
            sb.append('}');
         }
      }
      public boolean hasToString(Class<?> type) {
         try {
            Method method = type.getMethod("toString");
            if (method.getDeclaringClass() != Object.class) {
               return true;
            }
         } catch (Exception e) {
            // ignore it
         }
         return false;
      }
   }
   static class LengthLimiter {
      private int m_maxLength;
      private int m_maxItemLength;
      private int m_halfMaxItemLength;
      private StringBuilder m_sb;
      public LengthLimiter(StringBuilder sb, int maxLength, int maxItemLength) {
         m_sb = sb;
         m_maxLength = maxLength - 3;
         m_maxItemLength = maxItemLength;
         m_halfMaxItemLength = maxItemLength / 2 - 1;
      }
      public LengthLimiter append(char ch) {
         m_sb.append(ch);
         return this;
      }
      public LengthLimiter append(Object value) {
         append(value, false);
         return this;
      }
      public LengthLimiter append(Object value, boolean itemLimit) {
         int len = m_sb.length();
         String str = getString(value, itemLimit);
         if (m_maxLength > 0 && len + str.length() > m_maxLength) {
            throw new RuntimeException("Length limited.");
         } else {
            m_sb.append(str);
            return this;
         }
      }
      private String getString(Object value, boolean itemLimit) {
         String str = String.valueOf(value);
         if (itemLimit && m_maxItemLength > 0) {
            int len = str.length();
            if (len > m_maxItemLength) {
               return str.substring(0, m_halfMaxItemLength) + "..." + str.substring(len - m_halfMaxItemLength, len);
            }
         }
         return str;
      }
   }
}

+ 100 - 0
cat-client/src/main/java/com/site/lookup/util/StringUtils.java

@ -0,0 +1,100 @@
package com.site.lookup.util;
import java.util.Collection;
public class StringUtils {
   public static final boolean isEmpty(String str) {
      return str == null || str.length() == 0;
   }
   public static final boolean isNotEmpty(String str) {
      return str != null && str.length() > 0;
   }
   public static final String join(String[] array, String separator) {
      StringBuilder sb = new StringBuilder(1024);
      boolean first = true;
      for (String item : array) {
         if (first) {
            first = false;
         } else {
            sb.append(separator);
         }
         sb.append(item);
      }
      return sb.toString();
   }
   public static final String join(Collection<String> list, String separator) {
      StringBuilder sb = new StringBuilder(1024);
      boolean first = true;
      for (String item : list) {
         if (first) {
            first = false;
         } else {
            sb.append(separator);
         }
         sb.append(item);
      }
      return sb.toString();
   }
   public static final String normalizeSpace(String str) {
      int len = str.length();
      StringBuilder sb = new StringBuilder(len);
      boolean space = false;
      for (int i = 0; i < len; i++) {
         char ch = str.charAt(i);
         switch (ch) {
         case ' ':
         case '\t':
         case '\r':
         case '\n':
            space = true;
            break;
         default:
            if (space) {
               sb.append(' ');
               space = false;
            }
            sb.append(ch);
         }
      }
      return sb.toString();
   }
   public static final String trimAll(String str) {
      if (str == null) {
         return str;
      }
      int len = str.length();
      StringBuilder sb = new StringBuilder(len);
      for (int i = 0; i < len; i++) {
         char ch = str.charAt(i);
         switch (ch) {
         case ' ':
         case '\t':
         case '\r':
         case '\n':
            break;
         default:
            sb.append(ch);
         }
      }
      return sb.toString();
   }
}

+ 33 - 0
cat-client/src/main/resources/META-INF/dal/model/client-codegen.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<model>
  <entity name="config" root="true">
    <attribute name="mode" value-type="String" />
    <attribute name="enabled" value-type="boolean" />
    <attribute name="dump-locked" value-type="boolean" />
    <entity-ref name="server" type="list" names="servers" xml-indent="true" />
    <entity-ref name="domain" type="list" names="domains" />
    <entity-ref name="bind" />
    <entity-ref name="property" type="list" names="properties" xml-indent="true" />
  </entity>
  <entity name="server">
    <attribute name="ip" value-type="String" />
    <attribute name="port" value-type="int" />
    <attribute name="http-port" value-type="int" />
    <attribute name="enabled" value-type="boolean" />
  </entity>
  <entity name="domain">
    <attribute name="id" value-type="String" />
    <attribute name="ip" value-type="String" />
    <attribute name="enabled" value-type="boolean" />
    <attribute name="max-message-size" value-type="int" />
  </entity>
  <entity name="bind">
    <attribute name="ip" value-type="String" />
    <attribute name="port" value-type="int" />
  </entity>
  <entity name="property">
    <attribute name="name" value-type="String" />
    <element name="text" value-type="String" text="true" />
  </entity>
</model>

+ 6 - 0
cat-client/src/main/resources/META-INF/dal/model/client-manifest.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <file path="client-codegen.xml" />
  <file path="client-model.xml" />
</manifest>

+ 26 - 0
cat-client/src/main/resources/META-INF/dal/model/client-model.xml

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<model model-package="com.dianping.cat.configuration.client" enable-sax-parser="true" enable-validator="true" enable-merger="true">
	<entity name="config" class-name="ClientConfig" root="true" dynamic-attributes="true">
		<attribute name="mode" required="true" />
    	<attribute name="enabled" value-type="boolean" default-value="true"/>
		<element name="base-log-dir" value-type="String" default-value="target/catlog" />
		<entity-ref name="domain" type="map" names="domains" />
		<entity-ref name="property" type="map" names="properties" />
	</entity>
	<entity name="server">
		<attribute name="ip" value-type="String" key="true" />
	    <attribute name="port" value-type="int" default-value="2280" />
	    <attribute name="http-port" value-type="int" default-value="8080" />
    	<attribute name="enabled" value-type="boolean" default-value="true"/>
	</entity>
	<entity name="domain">
		<attribute name="id" value-type="String" key="true" />
    	<attribute name="max-message-size" value-type="int" primitive="true" default-value="1000" />
	</entity>
	<entity name="bind">
		<attribute name="port" primitive="true" default-value="2280" />
	</entity>
	<entity name="property">
		<attribute name="name" value-type="String" key="true" />
	</entity>
</model>

+ 65 - 0
cat-client/src/main/resources/META-INF/dal/model/status-codegen.xml

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<model>
  <entity name="status" root="true">
    <attribute name="timestamp" value-type="Date" format="yyyy-MM-dd HH:mm:ss.SSS" />
    <entity-ref name="runtime" />
    <entity-ref name="os" />
    <entity-ref name="disk" />
    <entity-ref name="memory" />
    <entity-ref name="thread" />
    <entity-ref name="message" />
  </entity>
  <entity name="runtime">
    <attribute name="start-time" value-type="int" />
    <attribute name="up-time" value-type="int" />
    <attribute name="java-version" value-type="String" />
    <attribute name="user-name" value-type="String" />
    <element name="user-dir" value-type="String" />
    <element name="java-classpath" value-type="String" />
  </entity>
  <entity name="os">
    <attribute name="name" value-type="String" />
    <attribute name="arch" value-type="String" />
    <attribute name="version" value-type="String" />
    <attribute name="available-processors" value-type="int" />
    <attribute name="system-load-average" value-type="double" />
    <attribute name="process-time" value-type="int" />
    <attribute name="total-physical-memory" value-type="int" />
    <attribute name="free-physical-memory" value-type="int" />
    <attribute name="committed-virtual-memory" value-type="int" />
    <attribute name="total-swap-space" value-type="int" />
    <attribute name="free-swap-space" value-type="int" />
  </entity>
  <entity name="memory">
    <attribute name="max" value-type="int" />
    <attribute name="total" value-type="int" />
    <attribute name="free" value-type="int" />
    <attribute name="heap-usage" value-type="int" />
    <attribute name="non-heap-usage" value-type="int" />
  </entity>
  <entity name="thread">
    <attribute name="count" value-type="int" />
    <attribute name="daemon-count" value-type="int" />
    <attribute name="peek-count" value-type="int" />
    <attribute name="total-started-count" value-type="int" />
    <attribute name="cat-thread-count" value-type="int" />
    <attribute name="pigeon-thread-count" value-type="int" />
    <attribute name="http-thread-count" value-type="int" />
    <element name="dump" value-type="String"/>
  </entity>
  <entity name="disk">
    <entity-ref name="disk-volume" type="list" names="disk-volumes" />
  </entity>
  <entity name="disk-volume">
    <attribute name="id" value-type="String" />
    <attribute name="total" value-type="long" />
    <attribute name="free" value-type="long" />
    <attribute name="usable" value-type="long" />
  </entity>
  <entity name="message">
    <attribute name="produced" value-type="int" />
    <attribute name="overflowed" value-type="int" />
    <attribute name="bytes" value-type="int" />
  </entity>
</model>

+ 6 - 0
cat-client/src/main/resources/META-INF/dal/model/status-manifest.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <file path="status-codegen.xml" />
  <file path="status-model.xml" />
</manifest>

+ 72 - 0
cat-client/src/main/resources/META-INF/dal/model/status-model.xml

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<model model-package="com.dianping.cat.status.model" enable-sax-parser="true" enable-merger="true" enable-model-test="true" enable-base-visitor="true">
	<entity name="status" class-name="StatusInfo">
		<attribute name="timestamp" value-type="Date" format="yyyy-MM-dd HH:mm:ss.SSS" />
	    <entity-ref name="runtime" />
	    <entity-ref name="os" />
	    <entity-ref name="disk" />
	    <entity-ref name="memory" />
	    <entity-ref name="thread" />
	    <entity-ref name="message" />
		<entity-ref name="extension" names ="extensions" type="map" method-find-or-create="true"/>
	</entity>
	<entity name="runtime" class-name="RuntimeInfo">
		<attribute name="start-time" value-type="long" primitive="true" />
		<attribute name="up-time" value-type="long" primitive="true" />
	</entity>
	<entity name="os" class-name="OsInfo">
		<attribute name="available-processors" value-type="int" primitive="true" />
		<attribute name="system-load-average" value-type="double" primitive="true" />
		<attribute name="process-time" value-type="long" primitive="true" />
		<attribute name="total-physical-memory" value-type="long" primitive="true" />
		<attribute name="free-physical-memory" value-type="long" primitive="true" />
		<attribute name="committed-virtual-memory" value-type="long" primitive="true" />
		<attribute name="total-swap-space" value-type="long" primitive="true" />
		<attribute name="free-swap-space" value-type="long" primitive="true" />
	</entity>
	<entity name="memory" class-name="MemoryInfo">
		<attribute name="max" value-type="long" primitive="true" />
		<attribute name="total" value-type="long" primitive="true" />
		<attribute name="free" value-type="long" primitive="true" />
		<attribute name="heap-usage" value-type="long" primitive="true" />
		<attribute name="non-heap-usage" value-type="long" primitive="true" />
		<entity-ref name="gc" names ="gcs" type="list"/>
	</entity>
	<entity name="gc" class-name="GcInfo">
		<attribute name="name" value-type="String" />
		<attribute name="count" value-type="long" primitive="true" />
		<attribute name="time" value-type="long" primitive="true" />
	</entity>
	<entity name="thread" class-name="ThreadsInfo">
		<attribute name="count" value-type="int" primitive="true" />
		<attribute name="daemon-count" value-type="int" primitive="true" />
		<attribute name="peek-count" value-type="int" primitive="true" />
		<attribute name="total-started-count" value-type="int" primitive="true" />
		<attribute name="cat-thread-count" value-type="int" primitive="true" />
		<attribute name="pigeon-thread-count" value-type="int" primitive="true" />
		<attribute name="http-thread-count" value-type="int" primitive="true" />
	</entity>
	<entity name="disk" class-name="DiskInfo">
		<entity-ref name="disk-volume" list="list" names="disk-volumes" />
	</entity>
	<entity name="disk-volume" class-name="DiskVolumeInfo">
		<attribute name="id" value-type="String" key="true" />
		<attribute name="total" value-type="long" primitive="true"/>
		<attribute name="free" value-type="long" primitive="true" />
		<attribute name="usable" value-type="long" primitive="true" />
	</entity>
	<entity name="message" class-name="MessageInfo">
		<attribute name="produced" value-type="long" primitive="true" />
		<attribute name="overflowed" value-type="long" primitive="true" />
		<attribute name="bytes" value-type="long" primitive="true" />
	</entity>
	<entity name="extension"  dynamic-attributes="true">
		<attribute name="id" value-type="String" key="true"/>
		<element name="description" value-type="String" escape="false"/>
		<entity-ref name="extensionDetail" names ="details" type="map" method-find-or-create="true"/>
	</entity>
	<entity name="extensionDetail" dynamic-attributes="true">
		<attribute name="id" value-type="String" key="true"/>
		<attribute name="value" value-type="double" primitive="true" />
	</entity>	
</model>

+ 111 - 0
cat-client/src/main/resources/META-INF/plexus/components-cat-client.xml

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- THIS FILE WAS AUTO GENERATED FROM class com.dianping.cat.build.ComponentsConfigurator, DO NOT EDIT IT -->
<plexus>
	<components>
		<component>
			<role>com.dianping.cat.configuration.ClientConfigManager</role>
			<implementation>com.dianping.cat.configuration.DefaultClientConfigManager</implementation>
		</component>
		<component>
			<role>com.dianping.cat.message.internal.MessageIdFactory</role>
			<implementation>com.dianping.cat.message.internal.MessageIdFactory</implementation>
		</component>
		<component>
			<role>com.dianping.cat.message.spi.MessageManager</role>
			<implementation>com.dianping.cat.message.internal.DefaultMessageManager</implementation>
			<requirements>
				<requirement>
					<role>com.dianping.cat.configuration.ClientConfigManager</role>
				</requirement>
				<requirement>
					<role>com.dianping.cat.message.io.TransportManager</role>
				</requirement>
				<requirement>
					<role>com.dianping.cat.message.internal.MessageIdFactory</role>
				</requirement>
			</requirements>
		</component>
		<component>
			<role>com.dianping.cat.message.MessageProducer</role>
			<implementation>com.dianping.cat.message.internal.DefaultMessageProducer</implementation>
			<requirements>
				<requirement>
					<role>com.dianping.cat.message.spi.MessageManager</role>
				</requirement>
				<requirement>
					<role>com.dianping.cat.message.internal.MessageIdFactory</role>
				</requirement>
			</requirements>
		</component>
		<component>
			<role>com.dianping.cat.message.io.TcpSocketSender</role>
			<implementation>com.dianping.cat.message.io.TcpSocketSender</implementation>
			<requirements>
				<requirement>
					<role>com.dianping.cat.configuration.ClientConfigManager</role>
				</requirement>
				<requirement>
					<role>com.dianping.cat.message.internal.MessageIdFactory</role>
				</requirement>
				<requirement>
					<role>com.dianping.cat.message.spi.MessageStatistics</role>
					<field-name>m_statistics</field-name>
				</requirement>
				<requirement>
					<role>com.dianping.cat.message.spi.MessageCodec</role>
					<role-hint>plain-text</role-hint>
					<field-name>m_codec</field-name>
				</requirement>
			</requirements>
		</component>
		<component>
			<role>com.dianping.cat.message.io.TransportManager</role>
			<implementation>com.dianping.cat.message.io.DefaultTransportManager</implementation>
			<requirements>
				<requirement>
					<role>com.dianping.cat.configuration.ClientConfigManager</role>
				</requirement>
				<requirement>
					<role>com.dianping.cat.message.io.TcpSocketSender</role>
				</requirement>
			</requirements>
		</component>
		<component>
			<role>com.dianping.cat.message.spi.MessageStatistics</role>
			<implementation>com.dianping.cat.message.spi.internal.DefaultMessageStatistics</implementation>
		</component>
		<component>
			<role>com.dianping.cat.status.StatusUpdateTask</role>
			<implementation>com.dianping.cat.status.StatusUpdateTask</implementation>
			<requirements>
				<requirement>
					<role>com.dianping.cat.message.spi.MessageStatistics</role>
				</requirement>
				<requirement>
					<role>com.dianping.cat.configuration.ClientConfigManager</role>
				</requirement>
			</requirements>
		</component>
		<component>
			<role>org.unidal.initialization.Module</role>
			<role-hint>cat-client</role-hint>
			<implementation>com.dianping.cat.CatClientModule</implementation>
		</component>
		<component>
			<role>com.dianping.cat.message.spi.codec.BufferWriter</role>
			<role-hint>escape</role-hint>
			<implementation>com.dianping.cat.message.spi.codec.EscapingBufferWriter</implementation>
		</component>
		<component>
			<role>com.dianping.cat.message.spi.MessageCodec</role>
			<role-hint>plain-text</role-hint>
			<implementation>com.dianping.cat.message.spi.codec.PlainTextMessageCodec</implementation>
			<requirements>
				<requirement>
					<role>com.dianping.cat.message.spi.codec.BufferWriter</role>
					<role-hint>escape</role-hint>
				</requirement>
			</requirements>
		</component>
	</components>
</plexus>

+ 15 - 0
cat-client/src/main/resources/META-INF/plexus/plexus.xml

@ -0,0 +1,15 @@
<plexus>
	<components>
		<component>
			<role>org.unidal.lookup.logging.LoggerManager</role>
			<implementation>org.unidal.lookup.logging.TimedConsoleLoggerManager</implementation>
			<configuration>
				<dateFormat>MM-dd HH:mm:ss.SSS</dateFormat>
				<showClass>true</showClass>
				<logFilePattern>cat_{0,date,yyyyMMdd}.log</logFilePattern>
				<baseDirRef>CAT_HOME</baseDirRef>
				<defaultBaseDir>/data/applogs/cat</defaultBaseDir>
			</configuration>
		</component>
	</components>
</plexus>

+ 5 - 0
cat-client/src/main/resources/META-INF/wizard/model/manifest.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
   <file path="wizard.xml" />
</manifest>

+ 3 - 0
cat-client/src/main/resources/META-INF/wizard/model/wizard.xml

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<wizard package="com.dianping.cat">
</wizard>

+ 49 - 0
cat-client/src/test/java/com/dianping/cat/AllTests.java

@ -0,0 +1,49 @@
package com.dianping.cat;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import com.dianping.cat.configuration.ConfigTest;
import com.dianping.cat.message.EventTest;
import com.dianping.cat.message.HeartbeatTest;
import com.dianping.cat.message.MessageTest;
import com.dianping.cat.message.TransactionTest;
import com.dianping.cat.message.internal.MockMessageBuilderTest;
import com.dianping.cat.message.spi.codec.PlainTextMessageCodecTest;
import com.dianping.cat.servlet.CatFilterTest;
@RunWith(Suite.class)
@SuiteClasses({
/* .message */
MessageTest.class,
EventTest.class,
HeartbeatTest.class,
TransactionTest.class,
/* .configuration */
ConfigTest.class,
MockMessageBuilderTest.class,
/* .spi.codec */
PlainTextMessageCodecTest.class,
/* .servlet */
CatFilterTest.class,
/* .tool */
ToolsTest.class,
CatTest.class,
ApiTest.class
})
public class AllTests {
}

+ 64 - 0
cat-client/src/test/java/com/dianping/cat/ApiTest.java

@ -0,0 +1,64 @@
package com.dianping.cat;
import java.util.HashMap;
import java.util.Map;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;
import com.dianping.cat.message.Transaction;
public class ApiTest {
	public Map<String, String> maps = new HashMap<String, String>();
	public Cat.Context context;
	@Before
	public void before() {
		context = new Cat.Context() {
			@Override
			public void addProperty(String key, String value) {
				maps.put(key, value);
			}
			@Override
			public String getProperty(String key) {
				return maps.get(key);
			}
		};
	}
	@Test
	public void testNoMessgeId() {
		Assert.assertEquals(null, Cat.getManager().getThreadLocalMessageTree().getMessageId());
		Transaction parent = Cat.newTransaction("Test", "test");
		Assert.assertEquals(null, Cat.getManager().getThreadLocalMessageTree().getMessageId());
		Cat.logRemoteCallClient(context);
		parent.complete();
	}
	@Test
	public void testRemoteCall() {
		Cat.getManager().reset();
		String parentMesageId = null;
		Transaction parent = Cat.newTransaction("Test", "test");
		Assert.assertEquals(null, Cat.getManager().getThreadLocalMessageTree().getMessageId());
		Cat.logRemoteCallClient(context);
		parentMesageId = Cat.getManager().getThreadLocalMessageTree().getMessageId();
		parent.complete();
		Transaction child = Cat.newTransaction("child", "child");
		Assert.assertEquals(null, Cat.getManager().getThreadLocalMessageTree().getMessageId());
		Cat.logRemoteCallServer(context);
		Cat.logRemoteCallClient(context);
		Assert.assertEquals(parentMesageId, Cat.getManager().getThreadLocalMessageTree().getParentMessageId());
		Assert.assertEquals(parentMesageId, Cat.getManager().getThreadLocalMessageTree().getRootMessageId());
		child.complete();
	}
}

+ 71 - 0
cat-client/src/test/java/com/dianping/cat/CatEnvironmentTest.java

@ -0,0 +1,71 @@
package com.dianping.cat;
import java.io.File;
import junit.framework.Assert;
import org.junit.Test;
import com.dianping.cat.message.MessageProducer;
import com.dianping.cat.message.Transaction;
public class CatEnvironmentTest {
	@Test
	public void testWithoutInitialize() throws InterruptedException {
		MessageProducer cat = Cat.getProducer();
		Transaction t = cat.newTransaction("TestType", "TestName");
		t.addData("data here");
		t.setStatus("TestStatus");
		t.complete();
		Thread.sleep(100);
		Assert.assertEquals(true, Cat.isInitialized());
		Cat.destroy();
	}
	@Test
	public void testWithInitialize() throws InterruptedException {
		Cat.initialize(new File("/data/appdatas/cat/client.xml"));
		MessageProducer cat = Cat.getProducer();
		Transaction t = cat.newTransaction("TestType", "TestName");
		t.addData("data here");
		t.setStatus("TestStatus");
		t.complete();
		Thread.sleep(100);
		Assert.assertEquals(true, Cat.isInitialized());
		Cat.destroy();
	}
	@Test
	public void testWithNoExistGlobalConfigInitialize() throws InterruptedException {
		Cat.initialize(new File("/data/appdatas/cat/clientNoExist.xml"));
		MessageProducer cat = Cat.getProducer();
		Transaction t = cat.newTransaction("TestType", "TestName");
		t.addData("data here");
		t.setStatus("TestStatus");
		t.complete();
		Thread.sleep(100);
		Assert.assertEquals(true, Cat.isInitialized());
		Cat.destroy();
	}
	
	
	@Test
	public void testJobTest() throws Exception{
		Cat.initialize("192.168.7.70","192.168.7.71");
		Transaction t = Cat.newTransaction("TestType", "TestName");
		t.addData("data here");
		t.setStatus("TestStatus");
		t.complete();
		Thread.sleep(10000);
	}
}

+ 35 - 0
cat-client/src/test/java/com/dianping/cat/CatTest.java

@ -0,0 +1,35 @@
package com.dianping.cat;
import junit.framework.Assert;
import org.junit.Test;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Trace;
public class CatTest {
	@Test
	public void test() {
		Cat.newTransaction("logTransaction", "logTransaction");
		Cat.newEvent("logEvent", "logEvent");
		Cat.newTrace("logTrace", "logTrace");
		Cat.newHeartbeat("logHeartbeat", "logHeartbeat");
		Throwable cause = new Throwable();
		Cat.logError(cause);
		Cat.logError("message", cause);
		Cat.logTrace("logTrace", "<trace>");
		Cat.logTrace("logTrace", "<trace>", Trace.SUCCESS, "data");
		Cat.logMetric("logMetric", "test", "test");
		Cat.logMetricForCount("logMetricForCount");
		Cat.logMetricForCount("logMetricForCount", 4);
		Cat.logMetricForDuration("logMetricForDuration", 100);
		Cat.logMetricForSum("logMetricForSum", 100);
		Cat.logMetricForSum("logMetricForSum", 100, 100);
		Cat.logEvent("RemoteLink", "Call", Message.SUCCESS, "Cat-0a010680-384736-2061");
		Cat.logEvent("EventType", "EventName");
		Cat.logHeartbeat("logHeartbeat", "logHeartbeat", Message.SUCCESS, null);
		Assert.assertEquals(true, Cat.isInitialized());
	}
}

+ 22 - 0
cat-client/src/test/java/com/dianping/cat/MessageFomatTest.java

@ -0,0 +1,22 @@
package com.dianping.cat;
import java.text.MessageFormat;
import java.text.ParseException;
import org.junit.Test;
public class MessageFomatTest {
	@Test
	public void test(){
		String str="/topic/s_c_2_0_r0123123123/123123123";
		MessageFormat format = new MessageFormat("/topic/{0}");
		
		try {
	      format.parse(str);
      } catch (ParseException e) {
	      e.printStackTrace();
      }
		
	}
}

+ 123 - 0
cat-client/src/test/java/com/dianping/cat/ToolsTest.java

@ -0,0 +1,123 @@
package com.dianping.cat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import junit.framework.Assert;
import org.junit.Test;
import com.site.helper.Splitters;
import com.site.helper.Stringizers;
import org.unidal.lookup.util.StringUtils;
public class ToolsTest {
	@Test
	public void testSplitters() {
		String str = "A;B;C;D;E;A;;B;F ";
		List<String> items = Splitters.by(";").noEmptyItem().trim().split(str);
		Assert.assertEquals(8, items.size());
		List<String> emptyItems = Splitters.by(';').trim().split(str);
		Assert.assertEquals(9, emptyItems.size());
	}
	@Test
	public void testStringizers() {
		Item item = new Item("aaa", "bbbbb", "ccccccccc");
		String[] array = { "aaa", "bbbbb", "ccccccccc" };
		List<String> list = Arrays.asList(array);
		Map<String, String> map = new LinkedHashMap<String, String>();
		map.put("a", "a");
		map.put("b", "b");
		map.put("c", "c");
		item.setArray(array);
		item.setList(list);
		item.setMap(map);
		String expected = "{\"a\": \"aaa\", \"array\": [\"aaa\", \"bbbbb\", \"c...c\"], \"b\": \"bbbbb\", \"c\": \"c...c\", \"list\": [\"aaa\", \"bbbbb\", \"c...c\"], \"map\": {\"a\": \"a\", \"b\": \"b\", \"c\": \"c\"}}";
		String str = Stringizers.forJson().from(item, 3, 5);
		Assert.assertEquals(expected, str);
	}
	@Test
	public void testStringUtils() {
		Assert.assertEquals(false, StringUtils.isEmpty("aa"));
		Assert.assertEquals(true, StringUtils.isNotEmpty("aa"));
		List<String> strs = new ArrayList<String>();
		String separator = ";";
		strs.add("A");
		strs.add("B");
		String joins = StringUtils.join(strs, separator);
		Assert.assertEquals("A;B", joins);
		String[] array = { "A", "B" };
		Assert.assertEquals("A;B", StringUtils.join(array, separator));
		Assert.assertEquals("AB", StringUtils.trimAll("A\t\n B"));
		Assert.assertEquals("A B", StringUtils.normalizeSpace("A\t\n B"));
	}
	public static class Item {
		private String a;
		private String b;
		private String c;
		private String[] array;
		private List<String> list;
		private Map<String, String> map;
		public Item(String a, String b, String c) {
			this.a = a;
			this.b = b;
			this.c = c;
		}
		public String getA() {
			return a;
		}
		public String[] getArray() {
			return array;
		}
		public String getB() {
			return b;
		}
		public String getC() {
			return c;
		}
		public List<String> getList() {
			return list;
		}
		public void setArray(String[] array) {
			this.array = array;
		}
		public void setList(List<String> list) {
			this.list = list;
		}
		public Map<String, String> getMap() {
			return map;
		}
		public void setMap(Map<String, String> map) {
			this.map = map;
		}
	}
}

+ 93 - 0
cat-client/src/test/java/com/dianping/cat/agent/MmapConsumerTaskTest.java

@ -0,0 +1,93 @@
package com.dianping.cat.agent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import org.junit.Test;
import org.unidal.lookup.ComponentTestCase;
import com.dianping.cat.message.internal.MessageIdFactory;
public class MmapConsumerTaskTest extends ComponentTestCase {
	private void createMessage(MessageIdFactory factory, StringBuilder sb, int i) {
		String status = (i % 7 == 0) || (i % 11 == 0) ? "50" + (i % 3) : "200";
		long t0 = System.currentTimeMillis();
		// <id>\t<parent-id>\t<root-id>\n
		sb.append(String.format("%s\t%s\t%s\n", factory.getNextId(), factory.getNextId(), factory.getNextId()));
		// <name>\t<status>\t<url>\t<request-header-len>\t<upstream-url>\t<response-header-len>\t<response-body-len>\t<response-body-blocks>\t<t0>\t<t1>\t<t2>\t<3>\t<t4>\n
		sb.append(String.format("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", //
		      "NginxTest", status, "http://url/here/" + i, i % 10, "http://upstream/url/here/" + (i % 3), //
		      i % 9, i % 8, i % 7, t0, t0 + i, t0 + 3 * i, t0 + 4 * i, t0 + 5 * i));
		// \n
		sb.append("\n");
	}
	@Test
	public void generateDataFile() throws Exception {
		File idx = new File("/data/appdatas/cat/mmap.idx");
		File dat = new File("/data/appdatas/cat/mmap.dat");
		MessageIdFactory factory = lookup(MessageIdFactory.class);
		StringBuilder sb = new StringBuilder(8192);
		factory.initialize("cat");
		for (int i = 0; i < 100; i++) {
			createMessage(factory, sb, i);
		}
		FileWriter datWriter = new FileWriter(dat);
		datWriter.write(sb.toString());
		datWriter.close();
		updateMmapIndex(idx, dat.length(), dat.length(), 0);
	}
	private void updateMmapIndex(File idx, long capacity, long writerIndex, long readerIndex) throws FileNotFoundException,
	      IOException {
		RandomAccessFile raf = new RandomAccessFile(idx, "rw");
		MappedByteBuffer buffer = raf.getChannel().map(MapMode.READ_WRITE, 0, 24);
		buffer.order(ByteOrder.LITTLE_ENDIAN);
		if (capacity > 0) {
			buffer.putLong(0, capacity);
		}
		if (writerIndex >= 0) {
			buffer.putLong(8, writerIndex);
		}
		if (readerIndex >= 0) {
			buffer.putLong(16, readerIndex);
		}
		buffer.force();
		raf.close();
	}
	@Test
	public void updateWriterIndex() throws Exception {
		File idx = new File("/data/appdatas/cat/mmap.idx");
		MessageIdFactory factory = lookup(MessageIdFactory.class);
		StringBuilder sb = new StringBuilder(8192);
		factory.initialize("cat");
		for (int i = 0; i < 50; i++) {
			createMessage(factory, sb, i);
		}
		updateMmapIndex(idx, -1, sb.length(), -1);
	}
}

+ 58 - 0
cat-client/src/test/java/com/dianping/cat/configuration/ConfigTest.java

@ -0,0 +1,58 @@
package com.dianping.cat.configuration;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import junit.framework.Assert;
import org.junit.Test;
import org.unidal.helper.Files;
import org.xml.sax.SAXException;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Server;
import com.dianping.cat.configuration.client.transform.DefaultSaxParser;
import com.dianping.cat.configuration.client.transform.DefaultXmlBuilder;
public class ConfigTest {
	@Test
	public void testClient() throws Exception {
		ClientConfig clientConfig = loadConfig("client-config.xml");
		ClientConfig globalConfig = loadConfig("global-config.xml");
		Assert.assertEquals("client", clientConfig.getMode());
		globalConfig.accept(new ClientConfigMerger(clientConfig));
		clientConfig.accept(new ClientConfigValidator());
		List<Server> servers = clientConfig.getServers();
		Assert.assertEquals(3, servers.size());
		Assert.assertEquals(2280, servers.get(0).getPort().intValue());
		Assert.assertEquals(true, servers.get(0).isEnabled());
		Assert.assertEquals(2281, servers.get(1).getPort().intValue());
		Assert.assertEquals(false, servers.get(1).isEnabled());
		Assert.assertEquals(2280, servers.get(2).getPort().intValue());
		Assert.assertEquals(true, servers.get(2).isEnabled());
	}
	private ClientConfig loadConfig(String configXml) throws IOException, SAXException {
		InputStream in = getClass().getResourceAsStream(configXml);
		String xml = Files.forIO().readFrom(in, "utf-8");
		ClientConfig clientConfig = DefaultSaxParser.parse(xml);
		return clientConfig;
	}
	@Test
	public void testConfig() throws Exception {
		String source = Files.forIO().readFrom(getClass().getResourceAsStream("config.xml"), "utf-8");
		ClientConfig root = DefaultSaxParser.parse(source);
		String xml = new DefaultXmlBuilder().buildXml(root);
		String expected = source;
		Assert.assertEquals("XML is not well parsed!", expected.replace("\r", ""), xml.replace("\r", ""));
	}
}

+ 87 - 0
cat-client/src/test/java/com/dianping/cat/message/AppSimulator.java

@ -0,0 +1,87 @@
package com.dianping.cat.message;
import static com.dianping.cat.message.Message.SUCCESS;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.dianping.cat.Cat;
@RunWith(JUnit4.class)
public class AppSimulator extends CatTestCase {
	@Test
	public void simulateHierarchyTransaction() throws Exception {
		MessageProducer cat = Cat.getProducer();
		Transaction t = cat.newTransaction("URL", "WebPage");
		String id1 = cat.createMessageId();
		String id2 = cat.createMessageId();
		try {
			// do your business here
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			Thread.sleep(5);
			cat.logEvent("Type1", "Name1", SUCCESS, "data1");
			cat.logEvent("Type2", "Name2", SUCCESS, "data2");
			cat.logEvent("RemoteCall", "Service1", SUCCESS, id1);
			createChildThreadTransaction(id1, cat.createMessageId(), cat.createMessageId());
			cat.logEvent("Type3", "Name3", SUCCESS, "data3");
			cat.logEvent("RemoteCall", "Service1", SUCCESS, id2);
			createChildThreadTransaction(id2, cat.createMessageId(), cat.createMessageId(), cat.createMessageId());
			cat.logEvent("Type4", "Name4", SUCCESS, "data4");
			cat.logEvent("Type5", "Name5", SUCCESS, "data5");
			t.setStatus(SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
	}
	protected void createChildThreadTransaction(final String id, final String... childIds) {
		Thread thread = new Thread() {
			@Override
			public void run() {
				MessageProducer cat = Cat.getProducer();
				Transaction t = cat.newTransaction("Service", "service-" + (int) (Math.random() * 10));
				// override the message id
				Cat.getManager().getThreadLocalMessageTree().setMessageId(id);
				try {
					// do your business here
					t.addData("service data here");
					Thread.sleep(5);
					cat.logEvent("Type1", "Name1", SUCCESS, "data1");
					cat.logEvent("Type2", "Name2", SUCCESS, "data2");
					for (String childId : childIds) {
						cat.logEvent("RemoteCall", "Service1", SUCCESS, childId);
						createChildThreadTransaction(childId);
					}
					cat.logEvent("Type4", "Name4", SUCCESS, "data4");
					cat.logEvent("Type5", "Name5", SUCCESS, "data5");
					t.setStatus(SUCCESS);
				} catch (Exception e) {
					t.setStatus(e);
				} finally {
					t.complete();
				}
			}
		};
		thread.start();
		// wait for it to complete
		try {
			thread.join();
		} catch (InterruptedException e) {
			// ignore it
		}
	}
}

+ 226 - 0
cat-client/src/test/java/com/dianping/cat/message/CatPerformanceTest.java

@ -0,0 +1,226 @@
package com.dianping.cat.message;
import static com.dianping.cat.message.Message.SUCCESS;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.LockSupport;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.dianping.cat.Cat;
@RunWith(JUnit4.class)
public class CatPerformanceTest {
	private int count = 100000;
	private int threadNumber = 4;
	private static int error = 0;
	@Before
	public void before() {
	}
	@After
	public void after() {
	}
	private void creatInternal() {
		MessageProducer cat = Cat.getProducer();
		Transaction t = cat.newTransaction("URL2", "WebPage");
		String id1 = cat.createMessageId();
		String id2 = cat.createMessageId();
		try {
			// do your business here
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			cat.logEvent("Type1", "Name1", SUCCESS, "data1");
			cat.logEvent("Type2", "Name2", SUCCESS, "data2");
			cat.logEvent("RemoteCall", "Service1", SUCCESS, id1);
			cat.logEvent("Type3", "Name3", SUCCESS, "data3");
			cat.logEvent("RemoteCall", "Service1", SUCCESS, id2);
			cat.logEvent("Type4", "Name4", SUCCESS, "data4");
			cat.logEvent("Type5", "Name5", SUCCESS, "data5");
			t.setStatus(SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
	}
	private void creatOneTransaction() {
		MessageProducer cat = Cat.getProducer();
		Transaction t = cat.newTransaction("URL4", "WebPage");
		String id1 = cat.createMessageId();
		String id2 = cat.createMessageId();
		try {
			// do your business here
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			creatInternal();
			cat.logEvent("Type1", "Name1", SUCCESS, "data1");
			cat.logEvent("Type2", "Name2", SUCCESS, "data2");
			cat.logEvent("RemoteCall", "Service1", SUCCESS, id1);
			cat.logEvent("Type3", "Name3", SUCCESS, "data3");
			cat.logEvent("RemoteCall", "Service1", SUCCESS, id2);
			cat.logEvent("Type4", "Name4", SUCCESS, "data4");
			cat.logEvent("Type5", "Name5", SUCCESS, "data5");
			t.setStatus(SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
	}
	@Test
	@Ignore
	public void justloop() throws InterruptedException {
		Cat.initialize(new File("/data/appdatas/cat/client.xml"));
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					while (true) {
						creatOneTransaction();
						LockSupport.parkNanos(10);
					}
				} finally {
				}
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					while (true) {
						creatOneTransaction();
						LockSupport.parkNanos(10);
					}
				} finally {
				}
			}
		}).start();
		Thread.sleep(1000000);
	}
	@Test
	@Ignore
	public void justloop2() throws InterruptedException {
		Cat.initialize(new File("/data/appdatas/cat/client.xml"));
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					creatOneTransaction();
				}
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					creatOneTransaction();
				}
			}
		}).start();
		Thread.sleep(1000000);
	}
	@Ignore
	@Test
	public void test() throws InterruptedException {
		Cat.initialize(new File("/data/appdatas/cat/client.xml"));
		long time = System.currentTimeMillis();
		for (int i = 0; i < count; i++) {
			creatOneTransaction();
		}
		long endtime = System.currentTimeMillis();
		System.out.println("avg:" + (double) (endtime - time) / (double) count + "ms");
		Thread.sleep(1000000);
	}
	@Test
	@Ignore
	public void testManyThread() throws IOException, InterruptedException {
		Cat.initialize(new File("/data/appdatas/cat/client.xml"));
		System.out.println("press any key to continue...");
		System.in.read();
		CountDownLatch start = new CountDownLatch(threadNumber);
		CountDownLatch end = new CountDownLatch(threadNumber);
		for (int i = 0; i < threadNumber; i++) {
			TestThread thread = new TestThread(start, end);
			thread.start();
			start.countDown();
		}
		try {
			end.await();
		} catch (InterruptedException e) {
			// ignore
		}
		System.out.println("Done with errors: " + error);
		Thread.sleep(10000);
	}
	class TestThread extends Thread {
		CountDownLatch m_end;
		CountDownLatch m_latch;
		public TestThread(CountDownLatch latch, CountDownLatch end) {
			m_latch = latch;
			m_end = end;
		}
		@Override
		public void run() {
			try {
				m_latch.await();
			} catch (InterruptedException e) {
				// ignore
			}
			long time = System.currentTimeMillis();
			for (int i = 0; i < count; i++) {
				creatOneTransaction();
			}
			long endtime = System.currentTimeMillis();
			System.out.println(Thread.currentThread().getName() + " avg: " + (double) (endtime - time) / (double) count
			      + "ms");
			m_end.countDown();
		}
	}
}

+ 62 - 0
cat-client/src/test/java/com/dianping/cat/message/CatTestCase.java

@ -0,0 +1,62 @@
package com.dianping.cat.message;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import org.junit.After;
import org.junit.Before;
import org.unidal.helper.Files;
import org.unidal.lookup.ComponentTestCase;
import com.dianping.cat.Cat;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.configuration.client.entity.Server;
public abstract class CatTestCase extends ComponentTestCase {
	protected File getConfigurationFile() {
		if (isCatServerAlive()) {
			try {
				ClientConfig config = new ClientConfig();
				config.setMode("client");
				config.addDomain(new Domain("cat"));
				config.addServer(new Server("localhost").setPort(2280));
				File file = new File("target/cat-config.xml");
				Files.forIO().writeTo(file, config.toString());
				return file;
			} catch (IOException e) {
				return null;
			}
		}
		return null;
	}
	protected boolean isCatServerAlive() {
		// detect if a CAT server listens on localhost:2280
		try {
			SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost", 2280));
			channel.close();
			return true;
		} catch (Exception e) {
			// ignore it
		}
		return false;
	}
	@Before
	public void setup() throws Exception {
		Cat.initialize(getContainer(), getConfigurationFile());
	}
	@After
	public void teardown() throws Exception {
	}
}

+ 34 - 0
cat-client/src/test/java/com/dianping/cat/message/EventTest.java

@ -0,0 +1,34 @@
package com.dianping.cat.message;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.dianping.cat.Cat;
@RunWith(JUnit4.class)
public class EventTest {
	@Test
	public void testNormal() {
		Event event = Cat.getProducer().newEvent("Review", "New");
		event.addData("id", 12345);
		event.addData("user", "john");
		event.setStatus(Message.SUCCESS);
		event.complete();
	}
	@Test
	public void testException() {
		Cat.getProducer().logError(new RuntimeException());
	}
	@Test
	public void testInOneShot() {
		// Normal case
		Cat.getProducer().logEvent("Review", "New", Message.SUCCESS, "id=12345&user=john");
		// Exception case
		Cat.getProducer().logError(new RuntimeException());
	}
}

+ 43 - 0
cat-client/src/test/java/com/dianping/cat/message/HeartbeatTest.java

@ -0,0 +1,43 @@
package com.dianping.cat.message;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.dianping.cat.Cat;
@RunWith(JUnit4.class)
public class HeartbeatTest {
	@Test
	public void testInOneShot() {
		Cat.getProducer().logHeartbeat("System", "Status", "0",
		      "ip=192.168.10.111&host=host-1&load=2.1&cpu=0.12,0.10&memory.total=2G&memory.free=456M");
	}
	@Test
	public void testService() {
		Heartbeat heartbeat = Cat.getProducer().newHeartbeat("Service", "ReviewService");
		heartbeat.addData("host", "192.168.10.112:1234");
		heartbeat.addData("weight", "20");
		heartbeat.addData("visits", "12345");
		heartbeat.addData("manifest", "addReview,getReview,getShopReviews");
		heartbeat.addData("more", "...");
		heartbeat.setStatus(Message.SUCCESS);
		heartbeat.complete();
	}
	@Test
	public void testStatus() {
		Heartbeat heartbeat = Cat.getProducer().newHeartbeat("System", "Status");
		heartbeat.addData("ip", "192.168.10.111");
		heartbeat.addData("host", "host-1");
		heartbeat.addData("load", "2.1");
		heartbeat.addData("cpu", "0.12,0.10");
		heartbeat.addData("memory.total", "2G");
		heartbeat.addData("memory.free", "456M");
		heartbeat.setStatus(Message.SUCCESS);
		heartbeat.complete();
	}
}

+ 324 - 0
cat-client/src/test/java/com/dianping/cat/message/MessageTest.java

@ -0,0 +1,324 @@
package com.dianping.cat.message;
import io.netty.buffer.ByteBuf;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import junit.framework.Assert;
import org.junit.Test;
import org.unidal.helper.Files;
import org.unidal.helper.Reflects;
import org.unidal.lookup.ComponentTestCase;
import org.unidal.lookup.PlexusContainer;
import com.dianping.cat.Cat;
import com.dianping.cat.configuration.ClientConfigManager;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.configuration.client.entity.Server;
import com.dianping.cat.message.internal.DefaultTransaction;
import com.dianping.cat.message.io.MessageSender;
import com.dianping.cat.message.io.TransportManager;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.MessageTree;
public class MessageTest extends ComponentTestCase {
	private Queue<MessageTree> m_queue = new LinkedBlockingQueue<MessageTree>();
	private void checkMessage(String expected) {
		StringBuilder sb = new StringBuilder(1024);
		MessageCodec codec = new MockMessageCodec(sb);
		while (true) {
			MessageTree tree = m_queue.poll();
			if (tree != null) {
				codec.encode(tree, null);
			} else {
				break;
			}
		}
		
		Assert.assertEquals(expected, sb.toString());
	}
	protected File getConfigurationFile() {
		try {
			ClientConfig config = new ClientConfig();
			config.setMode("client");
			config.addDomain(new Domain("cat").setMaxMessageSize(8));
			config.addServer(new Server("localhost"));
			File file = new File("target/cat-config.xml");
			Files.forIO().writeTo(file, config.toString());
			return file;
		} catch (IOException e) {
			throw new RuntimeException("Unable to create cat-config.xml file!");
		}
	}
	@Override
	public void setUp() throws Exception {
		super.setUp();
		
		defineComponent(TransportManager.class, null, MockTransportManager.class);
		MockTransportManager transportManager = (MockTransportManager) lookup(TransportManager.class);
		transportManager.setQueue(m_queue);
		File configurationFile = getConfigurationFile();
		Cat.initialize(configurationFile);
		ClientConfigManager configManager = lookup(ClientConfigManager.class);
		configManager.initialize(configurationFile);
		m_queue.clear();
		
		Reflects.forMethod().invokeDeclaredMethod(Cat.getInstance(), "setContainer", PlexusContainer.class, getContainer());
	}
	@Test
	public void testEvent() {
		Event event = Cat.getProducer().newEvent("Review", "New");
		event.addData("id", 12345);
		event.addData("user", "john");
		event.setStatus(Message.SUCCESS);
		event.complete();
		checkMessage("E Review New 0 id=12345&user=john\n");
	}
	@Test
	public void testHeartbeat() {
		Heartbeat heartbeat = Cat.getProducer().newHeartbeat("System", "Status");
		heartbeat.addData("ip", "192.168.10.111");
		heartbeat.addData("host", "host-1");
		heartbeat.addData("load", "2.1");
		heartbeat.addData("cpu", "0.12,0.10");
		heartbeat.addData("memory.total", "2G");
		heartbeat.addData("memory.free", "456M");
		heartbeat.setStatus(Message.SUCCESS);
		heartbeat.complete();
		checkMessage("H System Status 0 ip=192.168.10.111&host=host-1&load=2.1&cpu=0.12,0.10&memory.total=2G&memory.free=456M\n");
	}
	@Test
	public void testMessageTruncatedForDuration() throws IOException {
		Transaction t = Cat.newTransaction("URL", "MyPage");
		try {
			// do your business here
			t.addData("k1", "v1");
			for (int i = 0; i < 3; i++) {
				Cat.logEvent("Event", "Name" + i);
			}
			Transaction t1 = Cat.newTransaction("URL1", "MyPage");
			t1.setStatus(Message.SUCCESS);
			t1.complete();
			// move root transaction to one hour ago
			((DefaultTransaction) t).setTimestamp(t.getTimestamp() - 3600 * 1000L + 1);
			Transaction t2 = Cat.newTransaction("URL2", "MyPage");
			for (int i = 0; i < 3; i++) {
				Cat.logEvent("Event2", "Name" + i);
			}
			t2.setStatus(Message.SUCCESS);
			t2.complete();
			t.setStatus(Message.SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
		String expected = Files.forIO().readFrom(getClass().getResourceAsStream("message-truncated-for-duration.txt"), "utf-8");
		checkMessage(expected);
	}
	@Test
	public void testMessageTruncatedForSize() throws IOException {
		Transaction t = Cat.newTransaction("URL", "MyPage");
		try {
			// do your business here
			t.addData("k1", "v1");
			for (int i = 0; i < 20; i++) {
				Transaction t0 = Cat.newTransaction("URL0", "MyPage");
				t0.setStatus(Message.SUCCESS);
				t0.complete();
			}
			Transaction t1 = Cat.newTransaction("URL1", "MyPage");
			Transaction t2 = Cat.newTransaction("URL2", "MyPage");
			for (int i = 0; i < 20; i++) {
				Cat.logEvent("Event", "Name" + i);
			}
			t2.complete();
			t1.complete();
			t.setStatus(Message.SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
		String expected = Files.forIO().readFrom(getClass().getResourceAsStream("message-truncated-for-size.txt"), "utf-8");
		checkMessage(expected);
	}
	@Test
	public void testTransaction() throws Exception {
		Transaction t = Cat.newTransaction("URL", "MyPage");
		try {
			// do your business here
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			t.setStatus(Message.SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
		checkMessage("A URL MyPage 0 k1=v1&k2=v2&k3=v3\n");
	}
	protected static class MockMessageCodec implements MessageCodec {
		private StringBuilder m_sb;
		public MockMessageCodec(StringBuilder sb) {
			m_sb = sb;
		}
		@Override
		public MessageTree decode(ByteBuf buf) {
			throw new UnsupportedOperationException();
		}
		@Override
		public void decode(ByteBuf buf, MessageTree tree) {
			throw new UnsupportedOperationException();
		}
		@Override
		public void encode(MessageTree tree, ByteBuf buf) {
			encodeMessage(tree.getMessage(), buf);
		}
		private void encodeEvent(Event e, ByteBuf buf) {
			m_sb.append('E');
			m_sb.append(' ').append(e.getType());
			m_sb.append(' ').append(e.getName());
			m_sb.append(' ').append(e.getStatus());
			if (!e.getType().equals("RemoteCall") && !e.getType().equals("TruncatedTransaction")) {
				m_sb.append(' ').append(e.getData());
			}
			m_sb.append('\n');
		}
		private void encodeHeartbeat(Heartbeat h, ByteBuf buf) {
			m_sb.append('H');
			m_sb.append(' ').append(h.getType());
			m_sb.append(' ').append(h.getName());
			m_sb.append(' ').append(h.getStatus());
			m_sb.append(' ').append(h.getData());
			m_sb.append('\n');
		}
		private void encodeMessage(Message message, ByteBuf buf) {
			if (message instanceof Transaction) {
				encodeTransaction((Transaction) message, buf);
			} else if (message instanceof Event) {
				encodeEvent((Event) message, buf);
			} else if (message instanceof Heartbeat) {
				encodeHeartbeat((Heartbeat) message, buf);
			}
		}
		private void encodeTransaction(Transaction t, ByteBuf buf) {
			List<Message> children = t.getChildren();
			if (children.isEmpty()) {
				m_sb.append('A');
				m_sb.append(' ').append(t.getType());
				m_sb.append(' ').append(t.getName());
				m_sb.append(' ').append(t.getStatus());
				m_sb.append(' ').append(t.getData());
				m_sb.append('\n');
			} else {
				m_sb.append('t');
				m_sb.append(' ').append(t.getType());
				m_sb.append(' ').append(t.getName());
				m_sb.append('\n');
				for (Message message : children) {
					encodeMessage(message, buf);
				}
				m_sb.append('T');
				m_sb.append(' ').append(t.getType());
				m_sb.append(' ').append(t.getName());
				m_sb.append(' ').append(t.getStatus());
				m_sb.append(' ').append(t.getData());
				m_sb.append('\n');
			}
		}
	}
	public static class MockTransportManager implements TransportManager {
		private MessageSender m_sender;
		public MockTransportManager() {
		}
		@Override
		public MessageSender getSender() {
			return m_sender;
		}
		public void setQueue(final Queue<MessageTree> queue) {
			m_sender = new MessageSender() {
				@Override
				public void initialize() {
				}
				@Override
				public void send(MessageTree tree) {
					queue.offer(tree);
				}
				@Override
				public void shutdown() {
				}
			};
		}
	}
}

+ 20 - 0
cat-client/src/test/java/com/dianping/cat/message/MetricTest.java

@ -0,0 +1,20 @@
package com.dianping.cat.message;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.dianping.cat.Cat;
@RunWith(JUnit4.class)
public class MetricTest {
	@Test
	public void testNormal() {
		Cat.logMetric("order", "sum", 123, "count", 3);
	}
	@Test(expected = IllegalArgumentException.class)
	public void testException() {
		Cat.logMetric("order", "sum", 123, "count", 3, "key");
	}
}

+ 29 - 0
cat-client/src/test/java/com/dianping/cat/message/TransactionTest.java

@ -0,0 +1,29 @@
package com.dianping.cat.message;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.dianping.cat.Cat;
@RunWith(JUnit4.class)
public class TransactionTest {
	@Test
	public void testNormal() {
		Transaction t = Cat.getProducer().newTransaction("URL", "MyPage");
		try {
			// do your business here
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			Thread.sleep(30);
			t.setStatus(Message.SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
	}
}

+ 104 - 0
cat-client/src/test/java/com/dianping/cat/message/internal/CatClientTest.java

@ -0,0 +1,104 @@
package com.dianping.cat.message.internal;
import java.io.File;
import java.io.IOException;
import java.util.Queue;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.unidal.helper.Files;
import org.unidal.helper.Reflects;
import org.unidal.lookup.extension.Initializable;
import com.dianping.cat.Cat;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.message.CatTestCase;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.MessageProducer;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.io.TransportManager;
import com.dianping.cat.message.spi.MessageTree;
@RunWith(JUnit4.class)
public class CatClientTest extends CatTestCase {
	private Queue<MessageTree> m_queue;
	@BeforeClass
	public static void beforeClass() throws IOException {
		ClientConfig clientConfig = new ClientConfig();
		clientConfig.setMode("client");
		clientConfig.addDomain(new Domain("Test").setEnabled(true));
		File configFile = new File("/data/appdatas/cat/client.xml").getCanonicalFile();
		configFile.getParentFile().mkdirs();
		Files.forIO().writeTo(configFile, clientConfig.toString());
		// Cat.destroy();
		Cat.initialize(configFile);
	}
	@Before
	public void before() throws Exception {
		TransportManager manager = Cat.lookup(TransportManager.class);
		Initializable queue = Reflects.forField().getDeclaredFieldValue(manager.getSender().getClass(), "m_queue",
		      manager.getSender());
		queue.initialize();
		m_queue = Reflects.forField().getDeclaredFieldValue(queue.getClass(), "m_queue", queue);
	}
	public void testNormal() throws Exception {
		MessageProducer producer = Cat.getProducer();
		Transaction t = producer.newTransaction("URL", "MyPage");
		try {
			// do your business here
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			Thread.sleep(20);
			producer.logEvent("URL", "Payload", Message.SUCCESS, "host=my-host&ip=127.0.0.1&agent=...");
			t.setStatus(Message.SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
		// please stop CAT server when you run this test case
		Assert.assertEquals("One message should be in the queue.", 1, m_queue.size());
		MessageTree tree = m_queue.poll();
		Message m = tree.getMessage();
		Assert.assertTrue(Transaction.class.isAssignableFrom(m.getClass()));
		Transaction trans = (Transaction) m;
		Assert.assertEquals("URL", trans.getType());
		Assert.assertEquals("MyPage", trans.getName());
		Assert.assertEquals("0", trans.getStatus());
		Assert.assertTrue(trans.getDurationInMillis() > 0);
		Assert.assertEquals("k1=v1&k2=v2&k3=v3", trans.getData().toString());
		Assert.assertEquals(1, trans.getChildren().size());
		Message c = trans.getChildren().get(0);
		Assert.assertEquals("URL", c.getType());
		Assert.assertEquals("Payload", c.getName());
		Assert.assertEquals("0", c.getStatus());
		Assert.assertEquals("host=my-host&ip=127.0.0.1&agent=...", c.getData().toString());
	}
}

+ 171 - 0
cat-client/src/test/java/com/dianping/cat/message/internal/MessageIdFactoryTest.java

@ -0,0 +1,171 @@
package com.dianping.cat.message.internal;
import java.io.IOException;
import junit.framework.Assert;
import org.junit.Test;
public class MessageIdFactoryTest {
	private long m_timestamp = 1330327814748L;
	final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
	private MessageIdFactory m_factory = new MessageIdFactory() {
		@Override
		protected long getTimestamp() {
			return m_timestamp;
		}
	};
	@Test
	public void test() {
		String id = "UNKNOWN-c0a82050-376665-314";
		MessageId message = MessageId.parse(id);
		Assert.assertEquals(1355994000000L, message.getTimestamp());
		Assert.assertEquals("192.168.32.80", message.getIpAddress());
		Assert.assertEquals(2, message.getVersion());
		Assert.assertEquals(id, message.toString());
		id = "ARCH-UNKNOWN-c0a82050-376665-314";
		message = MessageId.parse(id);
		Assert.assertEquals(1355994000000L, message.getTimestamp());
		Assert.assertEquals("192.168.32.80", message.getIpAddress());
		Assert.assertEquals(2, message.getVersion());
		Assert.assertEquals("ARCH-UNKNOWN", message.getDomain());
		Assert.assertEquals(id, message.toString());
	}
	private void check(String domain, String expected) {
		m_factory.setDomain(domain);
		m_factory.setIpAddress("c0a83f99"); // 192.168.63.153
		String actual = m_factory.getNextId().toString();
		Assert.assertEquals(expected, actual);
		MessageId id = MessageId.parse(actual);
		Assert.assertEquals(domain, id.getDomain());
		Assert.assertEquals("c0a83f99", id.getIpAddressInHex());
		Assert.assertEquals(m_timestamp, id.getTimestamp());
	}
	@Test
	public void testNextId() throws Exception {
		m_factory.initialize("test");
		check("domain1", "domain1-c0a83f99-1330327814748-0");
		check("domain1", "domain1-c0a83f99-1330327814748-1");
		check("domain1", "domain1-c0a83f99-1330327814748-2");
		check("domain1", "domain1-c0a83f99-1330327814748-3");
		m_timestamp++;
		check("domain1", "domain1-c0a83f99-1330327814749-0");
		check("domain1", "domain1-c0a83f99-1330327814749-1");
		check("domain1", "domain1-c0a83f99-1330327814749-2");
		m_timestamp++;
		check("domain1", "domain1-c0a83f99-1330327814750-0");
		check("domain1", "domain1-c0a83f99-1330327814750-1");
		check("domain1", "domain1-c0a83f99-1330327814750-2");
	}
	@Test
	public void testNextIdContinousIncrement() throws IOException {
		MessageIdFactory f1 = new MessageIdFactory();
		f1.initialize("test");
		String id1 = f1.getNextId();
		String id2 = f1.getNextId();
		f1.close();
		MessageIdFactory f2 = new MessageIdFactory();
		f2.initialize("test");
		String id3 = f2.getNextId();
		String id4 = f2.getNextId();
		// f2.close();
		Assert.assertEquals(false, id1.equals(id2));
		Assert.assertEquals(false, id3.equals(id4));
		Assert.assertEquals(false, id1.equals(id3));
		Assert.assertEquals(false, id2.equals(id4));
	}
	@Test(timeout = 500)
	public void test_performance() throws IOException {
		MessageIdFactory f1 = new MessageIdFactory();
		f1.initialize("test");
		for (int i = 0; i < 10000; i++) {
			f1.getNextId();
		}
	}
	@Test
	public void testToHexString() {
		checkHexString(0, "0");
		checkHexString(m_timestamp++, "135bdb7825c");
		checkHexString(m_timestamp++, "135bdb7825d");
		checkHexString(m_timestamp++, "135bdb7825e");
		checkHexString(m_timestamp++, "135bdb7825f");
		checkHexString(m_timestamp++, "135bdb78260");
	}
	@Test
	public void testGetIpAddress() {
		for (int i = 0; i < 1000000; i++) {
			MessageId id = new MessageId(null, "ffffffff", m_timestamp, 0);
			Assert.assertEquals("255.255.255.255", id.getIpAddress());
			id = new MessageId(null, "11f111f1", m_timestamp, 0);
			Assert.assertEquals("17.241.17.241", id.getIpAddress());
		}
	}
	private void checkHexString(long value, String expected) {
		StringBuilder sb = new StringBuilder();
		toHexString(sb, value);
		String hex = sb.toString();
		Assert.assertEquals(Long.toHexString(value), hex);
		Assert.assertEquals(expected, hex);
	}
	void toHexString(StringBuilder sb, long value) {
		int offset = sb.length();
		do {
			int index = (int) (value & 0x0F);
			sb.append(digits[index]);
			value >>>= 4;
		} while (value != 0);
		int len = sb.length();
		while (offset < len) {
			char ch1 = sb.charAt(offset);
			char ch2 = sb.charAt(len - 1);
			sb.setCharAt(offset, ch2);
			sb.setCharAt(len - 1, ch1);
			offset++;
			len--;
		}
	}
}

+ 147 - 0
cat-client/src/test/java/com/dianping/cat/message/internal/MessageProducerTest.java

@ -0,0 +1,147 @@
package com.dianping.cat.message.internal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.io.File;
import java.io.IOException;
import java.util.Queue;
import java.util.Stack;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.unidal.helper.Files;
import org.unidal.helper.Reflects;
import org.unidal.lookup.extension.Initializable;
import com.dianping.cat.Cat;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.message.CatTestCase;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.MessageProducer;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.io.TransportManager;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.MessageTree;
@RunWith(JUnit4.class)
public class MessageProducerTest extends CatTestCase {
	private Queue<MessageTree> m_queue;
	@BeforeClass
	public static void beforeClass() throws IOException {
		ClientConfig clientConfig = new ClientConfig();
		clientConfig.setMode("client");
		clientConfig.addDomain(new Domain("Test").setEnabled(true));
		File configFile = new File("target/client.xml").getCanonicalFile();
		configFile.getParentFile().mkdirs();
		Files.forIO().writeTo(configFile, clientConfig.toString());
		Cat.destroy();
		Cat.initialize(configFile);
	}
	@Before
	public void before() throws Exception {
		TransportManager manager = Cat.lookup(TransportManager.class);
		Initializable queue = Reflects.forField().getDeclaredFieldValue(manager.getSender().getClass(), "m_queue",
		      manager.getSender());
		queue.initialize();
		m_queue = Reflects.forField().getDeclaredFieldValue(queue.getClass(), "m_queue", queue);
	}
	@Test
	public void testNormal() throws Exception {
		MessageProducer producer = Cat.getProducer();
		Transaction t = producer.newTransaction("URL", "MyPage");
		try {
			// do your business here
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			Thread.sleep(20);
			producer.logEvent("URL", "Payload", Message.SUCCESS, "host=my-host&ip=127.0.0.1&agent=...");
			t.setStatus(Message.SUCCESS);
		} catch (Exception e) {
			t.setStatus(e);
		} finally {
			t.complete();
		}
		// please stop CAT server when you run this test case
		Assert.assertEquals("One message should be in the queue.", 1, m_queue.size());
		MessageTree tree = m_queue.poll();
		Message m = tree.getMessage();
		Assert.assertTrue(Transaction.class.isAssignableFrom(m.getClass()));
		Transaction trans = (Transaction) m;
		Assert.assertEquals("URL", trans.getType());
		Assert.assertEquals("MyPage", trans.getName());
		Assert.assertEquals("0", trans.getStatus());
		Assert.assertTrue(trans.getDurationInMillis() > 0);
		Assert.assertEquals("k1=v1&k2=v2&k3=v3", trans.getData().toString());
		Assert.assertEquals(1, trans.getChildren().size());
		Message c = trans.getChildren().get(0);
		Assert.assertEquals("URL", c.getType());
		Assert.assertEquals("Payload", c.getName());
		Assert.assertEquals("0", c.getStatus());
		Assert.assertEquals("host=my-host&ip=127.0.0.1&agent=...", c.getData().toString());
	}
	@Test
	public void testNested() throws Exception {
		Stack<Transaction> stack = new Stack<Transaction>();
		for (int i = 0; i < 10; i++) {
			Transaction t = Cat.getProducer().newTransaction("Test", "TestName");
			t.addData("k1", "v1");
			t.addData("k2", "v2");
			t.addData("k3", "v3");
			stack.push(t);
		}
		while (!stack.isEmpty()) {
			Transaction t = stack.pop();
			t.setStatus(Message.SUCCESS);
			t.complete();
		}
		// please stop CAT server when you run this test case
		Assert.assertEquals("One message should be in the queue.", 1, m_queue.size());
		MessageTree tree = m_queue.poll();
		MessageCodec codec = Cat.lookup(MessageCodec.class, "plain-text");
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(4 * 1024);
		codec.encode(tree, buf);
		buf.readInt();
		MessageTree tree2 = codec.decode(buf);
		Assert.assertEquals(tree.toString(), tree2.toString());
	}
}

+ 72 - 0
cat-client/src/test/java/com/dianping/cat/message/internal/MockMessageBuilderTest.java

@ -0,0 +1,72 @@
package com.dianping.cat.message.internal;
import junit.framework.Assert;
import org.junit.Test;
import com.dianping.cat.message.Message;
public class MockMessageBuilderTest {
	@Test
	public void test() {
		Message message = new MockMessageBuilder() {
			@Override
			public MessageHolder define() {
				TransactionHolder t = t("WEB CLUSTER", "GET", 112819) //
				      .at(1348374838231L) //
				      .after(1300).child(t("QUICKIE SERVICE", "gimme_stuff", 1571)) //
				      .after(100).child(e("SERVICE", "event1")) //
				      .after(100).child(h("SERVICE", "heartbeat1")) //
				      .after(100).child(t("WEB SERVER", "GET", 109358) //
				            .after(1000).child(t("SOME SERVICE", "get", 4345) //
				                  .after(4000).child(t("MEMCACHED", "Get", 279))) //
				            .mark().after(200).child(t("MEMCACHED", "Inc", 319)) //
				            .reset().after(500).child(t("BIG ASS SERVICE", "getThemDatar", 97155) //
				                  .after(1000).mark().child(t("SERVICE", "getStuff", 3760)) //
				                  .reset().child(t("DATAR", "findThings", 94537)) //
				                  .after(200).child(t("THINGIE", "getMoar", 1435)) //
				            ) //
				            .after(100).mark().child(t("OTHER DATA SERVICE", "get", 4394) //
				                  .after(1000).mark().child(t("MEMCACHED", "Get", 378)) //
				                  .reset().child(t("MEMCACHED", "Get", 3496)) //
				            ) //
				            .reset().child(t("FINAL DATA SERVICE", "get", 4394) //
				                  .after(1000).mark().child(t("MEMCACHED", "Get", 386)) //
				                  .reset().child(t("MEMCACHED", "Get", 322)) //
				                  .reset().child(t("MEMCACHED", "Get", 322)) //
				            ) //
				      ) //
				;
				return t;
			}
		}.build();
		Assert.assertEquals("t2012-09-23 12:33:58.231	WEB CLUSTER	GET	\n" + //
		      "A2012-09-23 12:33:58.232	QUICKIE SERVICE	gimme_stuff	0	1571us		\n" + //
		      "E2012-09-23 12:33:58.233	SERVICE	event1	0		\n" + //
		      "H2012-09-23 12:33:58.234	SERVICE	heartbeat1	0		\n" + //
		      "t2012-09-23 12:33:58.234	WEB SERVER	GET	\n" + //
		      "t2012-09-23 12:33:58.235	SOME SERVICE	get	\n" + //
		      "A2012-09-23 12:33:58.239	MEMCACHED	Get	0	279us		\n" + //
		      "T2012-09-23 12:33:58.239	SOME SERVICE	get	0	4345us		\n" + //
		      "A2012-09-23 12:33:58.239	MEMCACHED	Inc	0	319us		\n" + //
		      "t2012-09-23 12:33:58.240	BIG ASS SERVICE	getThemDatar	\n" + //
		      "A2012-09-23 12:33:58.241	SERVICE	getStuff	0	3760us		\n" + //
		      "A2012-09-23 12:33:58.241	DATAR	findThings	0	94537us		\n" + //
		      "A2012-09-23 12:33:58.335	THINGIE	getMoar	0	1435us		\n" + //
		      "T2012-09-23 12:33:58.337	BIG ASS SERVICE	getThemDatar	0	97155us		\n" + //
		      "t2012-09-23 12:33:58.337	OTHER DATA SERVICE	get	\n" + //
		      "A2012-09-23 12:33:58.338	MEMCACHED	Get	0	378us		\n" + //
		      "A2012-09-23 12:33:58.338	MEMCACHED	Get	0	3496us		\n" + //
		      "T2012-09-23 12:33:58.341	OTHER DATA SERVICE	get	0	4394us		\n" + //
		      "t2012-09-23 12:33:58.337	FINAL DATA SERVICE	get	\n" + //
		      "A2012-09-23 12:33:58.338	MEMCACHED	Get	0	386us		\n" + //
		      "A2012-09-23 12:33:58.338	MEMCACHED	Get	0	322us		\n" + //
		      "A2012-09-23 12:33:58.338	MEMCACHED	Get	0	322us		\n" + //
		      "T2012-09-23 12:33:58.341	FINAL DATA SERVICE	get	0	4394us		\n" + //
		      "T2012-09-23 12:33:58.343	WEB SERVER	GET	0	109358us		\n" + //
		      "T2012-09-23 12:33:58.343	WEB CLUSTER	GET	0	112819us		\n" + //
		      "", message.toString().replace("\r", ""));
	}
}

+ 82 - 0
cat-client/src/test/java/com/dianping/cat/message/spi/codec/MessageCodecPerformanceTest.java

@ -0,0 +1,82 @@
package com.dianping.cat.message.spi.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.junit.Test;
import com.dianping.cat.message.CatTestCase;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.internal.MockMessageBuilder;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.MessageTree;
import com.dianping.cat.message.spi.internal.DefaultMessageTree;
public class MessageCodecPerformanceTest extends CatTestCase {
	public static final String ID = "plain-text";
	@Test
	public void testCodePerformance() throws Exception {
		MessageCodec codec = lookup(MessageCodec.class, ID);
		MessageTree tree = buildMessage();
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10240);
		
		codec.encode(tree, buf);
		int count = 5000000;
		for (int i = 0; i < count; i++) {
			buf.markReaderIndex();
			// read the size of the message
			buf.readInt();
			DefaultMessageTree result = (DefaultMessageTree) codec.decode(buf);
			buf.resetReaderIndex();
			result.setBuffer(buf);
		}
	}
	public MessageTree buildMessage() {
		Message message = new MockMessageBuilder() {
			@Override
			public MessageHolder define() {
				TransactionHolder t = t("WEB CLUSTER", "GET", 112819) //
				      .at(1348374838231L) //
				      .after(1300).child(t("QUICKIE SERVICE", "gimme_stuff", 1571)) //
				      .after(100).child(e("SERVICE", "event1")) //
				      .after(100).child(h("SERVICE", "heartbeat1")) //
				      .after(100).child(t("WEB SERVER", "GET", 109358) //
				            .after(1000).child(t("SOME SERVICE", "get", 4345) //
				                  .after(4000).child(t("MEMCACHED", "Get", 279))) //
				            .mark().after(200).child(t("MEMCACHED", "Inc", 319)) //
				            .reset().after(500).child(t("BIG ASS SERVICE", "getThemDatar", 97155) //
				                  .after(1000).mark().child(t("SERVICE", "getStuff", 3760)) //
				                  .reset().child(t("DATAR", "findThings", 94537)) //
				                  .after(200).child(t("THINGIE", "getMoar", 1435)) //
				            ) //
				            .after(100).mark().child(t("OTHER DATA SERVICE", "get", 4394) //
				                  .after(1000).mark().child(t("MEMCACHED", "Get", 378)) //
				                  .reset().child(t("MEMCACHED", "Get", 3496)) //
				            ) //
				            .reset().child(t("FINAL DATA SERVICE", "get", 4394) //
				                  .after(1000).mark().child(t("MEMCACHED", "Get", 386)) //
				                  .reset().child(t("MEMCACHED", "Get", 322)) //
				                  .reset().child(t("MEMCACHED", "Get", 322)) //
				            ) //
				      ) //
				;
				return t;
			}
		}.build();
		MessageTree tree = new DefaultMessageTree();
		tree.setDomain("cat");
		tree.setHostName("test");
		tree.setIpAddress("test");
		tree.setThreadGroupName("test");
		tree.setThreadId("test");
		tree.setThreadName("test");
		tree.setMessage(message);
		return tree;
	}
}

+ 76 - 0
cat-client/src/test/java/com/dianping/cat/message/spi/codec/MessageCodecUnexceptedCharTest.java

@ -0,0 +1,76 @@
package com.dianping.cat.message.spi.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import junit.framework.Assert;
import org.junit.Test;
import com.dianping.cat.message.CatTestCase;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.internal.MockMessageBuilder;
import com.dianping.cat.message.spi.MessageCodec;
import com.dianping.cat.message.spi.MessageTree;
import com.dianping.cat.message.spi.internal.DefaultMessageTree;
public class MessageCodecUnexceptedCharTest extends CatTestCase {
	public static final String ID = "plain-text";
	@Test
	public void testCodePerformance() throws Exception {
		MessageCodec codec = lookup(MessageCodec.class, ID);
		MessageTree tree = buildMessage();
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10240);
		
		codec.encode(tree, buf);
		MessageTree result = new DefaultMessageTree();
		codec.decode(buf, result);
		Assert.assertEquals(tree.toString(), result.toString());
	}
	public MessageTree buildMessage() {
		Message message = new MockMessageBuilder() {
			@Override
			public MessageHolder define() {
				TransactionHolder t = t("\n\nWEBC$$$$LUSTER\t\n", "GET\t\n",
				      "This&123123&1231&3&\n\n\n\n&\t\t\t&&&&\n\n\n\n\n\n is test data\t\t\n\n", 112819) //
				      .at(1348374838231L) //
				      .after(1300).child(t("QUICKIESERVICE", "gimme_stuff", 1571)) //
				      .after(100).child(e("SERVICE", "event1", "This\n\n\n\n\n\n is test data\t\t\n\n")) //
				      .after(100).child(h("SERVICE", "heartbeat1")) //
				      .after(100).child(t("WEB SERVER", "GET", 109358) //
				            .after(1000).child(t("SOME SERVICE", "get", 4345) //
				                  .after(4000).child(t("MEMCACHED", "Get", 279))) //
				            .mark().after(200).child(t("MEMCACHED", "Inc", 319)) //
				            .reset().after(500).child(t("BIG ASS SERVICE", "getThemDatar", 97155) //
				                  .after(1000).mark().child(t("SERVICE", "getStuff", 3760)) //
				                  .reset().child(t("DATAR", "findThings", 94537)) //
				                  .after(200).child(t("THINGIE", "getMoar", 1435)) //
				            ) //
				            .after(100).mark().child(t("OTHER DATA SERVICE", "get", 4394) //
				                  .after(1000).mark().child(t("MEMCACHED", "Get", 378)) //
				                  .reset().child(t("MEMCACHED", "Get", 3496)) //
				            ) //
				            .reset().child(t("FINAL DATA SERVICE", "get", 4394) //
				                  .after(1000).mark().child(t("MEMCACHED", "Get", 386)) //
				                  .reset().child(t("MEMCACHED", "Get", 322)) //
				                  .reset().child(t("MEMCACHED", "Get", 322)) //
				            ) //
				      ) //
				;
				return t;
			}
		}.build();
		MessageTree tree = new DefaultMessageTree();
		tree.setDomain("cat");
		tree.setHostName("test");
		tree.setIpAddress("test");
		tree.setThreadGroupName("test");
		tree.setThreadId("test");
		tree.setThreadName("test");
		tree.setMessage(message);
		return tree;
	}
}

+ 224 - 0
cat-client/src/test/java/com/dianping/cat/message/spi/codec/PlainTextMessageCodecTest.java

@ -0,0 +1,224 @@
package com.dianping.cat.message.spi.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.nio.charset.Charset;
import junit.framework.Assert;
import org.junit.Test;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Heartbeat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Metric;
import com.dianping.cat.message.Trace;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.message.internal.DefaultEvent;
import com.dianping.cat.message.internal.DefaultHeartbeat;
import com.dianping.cat.message.internal.DefaultMetric;
import com.dianping.cat.message.internal.DefaultTrace;
import com.dianping.cat.message.internal.DefaultTransaction;
import com.dianping.cat.message.spi.MessageTree;
import com.dianping.cat.message.spi.codec.PlainTextMessageCodec.Context;
import com.dianping.cat.message.spi.internal.DefaultMessageTree;
public class PlainTextMessageCodecTest {
	private void check(Message message, String expected) {
		PlainTextMessageCodec codec = new PlainTextMessageCodec();
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10240);
		Context ctx = new Context().setBuffer(buf);
		codec.setBufferWriter(new EscapingBufferWriter());
		codec.encodeMessage(message, buf);
		String actual = buf.toString(Charset.forName("utf-8"));
		Assert.assertEquals(expected, actual);
		MessageTree tree = new DefaultMessageTree();
		codec.decodeMessage(ctx, tree);
		Assert.assertEquals(expected, tree.getMessage().toString());
	}
	private void checkTree(MessageTree tree, String expected) {
		PlainTextMessageCodec codec = new PlainTextMessageCodec();
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10240);
		codec.encode(tree, buf);
		buf.readInt(); // get rid of length
		String actual = buf.toString(Charset.forName("utf-8"));
		Assert.assertEquals(expected, actual);
		MessageTree t = codec.decode(buf);
		Assert.assertEquals(expected, t.toString());
	}
	private Event newEvent(String type, String name, long timestamp, String status, String data) {
		DefaultEvent event = new DefaultEvent(type, name);
		event.setStatus(status);
		event.addData(data);
		event.setTimestamp(timestamp);
		return event;
	}
	private Metric newMetric(String type, String name, long timestamp, String status, String data) {
		DefaultMetric Metric = new DefaultMetric(type, name);
		Metric.setStatus(status);
		Metric.addData(data);
		Metric.setTimestamp(timestamp);
		return Metric;
	}
	private Heartbeat newHeartbeat(String type, String name, long timestamp, String status, String data) {
		DefaultHeartbeat heartbeat = new DefaultHeartbeat(type, name);
		heartbeat.setStatus(status);
		heartbeat.addData(data);
		heartbeat.setTimestamp(timestamp);
		return heartbeat;
	}
	private MessageTree newMessageTree() {
		MessageTree tree = new DefaultMessageTree();
		tree.setDomain("domain");
		tree.setHostName("hostName");
		tree.setIpAddress("ipAddress");
		tree.setMessageId("messageId");
		tree.setParentMessageId("parentMessageId");
		tree.setRootMessageId("rootMessageId");
		tree.setSessionToken("sessionToken");
		tree.setThreadGroupName("threadGroupName");
		tree.setThreadId("threadId");
		tree.setThreadName("threadName");
		return tree;
	}
	private Trace newTrace(String type, String name, long timestamp, String status, String data) {
		DefaultTrace trace = new DefaultTrace(type, name);
		trace.setStatus(status);
		trace.addData(data);
		trace.setTimestamp(timestamp);
		return trace;
	}
	private Transaction newTransaction(String type, String name, long timestamp, String status, int duration, String data) {
		DefaultTransaction transaction = new DefaultTransaction(type, name, null);
		transaction.setStatus(status);
		transaction.addData(data);
		transaction.complete();
		transaction.setTimestamp(timestamp);
		transaction.setDurationInMillis(duration);
		return transaction;
	}
	@Test
	public void testEvent() {
		long timestamp = 1325489621987L;
		Event event = newEvent("type", "name", timestamp, "0", "here is the data.");
		check(event, "E2012-01-02 15:33:41.987\ttype\tname\t0\there is the data.\t\n");
	}
	@Test
	public void testMetric() {
		long timestamp = 1325489621987L;
		Metric metric = newMetric("type", "name", timestamp, "0", "here is the data.");
		check(metric, "M2012-01-02 15:33:41.987\ttype\tname\t0\there is the data.\t\n");
	}
	@Test
	public void testEventForRawData() {
		long timestamp = 1325489621987L;
		String trace = "java.lang.Exception\n\tat com.dianping.cat.message.spi.codec.PlainTextMessageCodecTest.testEventForException(PlainTextMessageCodecTest.java:112)\n";
		Event event = newEvent("Exception", Exception.class.getName(), timestamp, "ERROR", trace);
		check(event,
		      "E2012-01-02 15:33:41.987\tException\tjava.lang.Exception\tERROR\t" + //
		            "java.lang.Exception\\n\\tat com.dianping.cat.message.spi.codec.PlainTextMessageCodecTest.testEventForException(PlainTextMessageCodecTest.java:112)\\n\t\n");
	}
	@Test
	public void testHeartbeat() {
		long timestamp = 1325489621987L;
		Heartbeat heartbeat = newHeartbeat("type", "name", timestamp, "0", "here is the data.");
		check(heartbeat, "H2012-01-02 15:33:41.987\ttype\tname\t0\there is the data.\t\n");
	}
	@Test
	public void testMessageTree() {
		MessageTree tree = newMessageTree();
		long timestamp = 1325489621987L;
		String expected = "PT1\tdomain\thostName\tipAddress\tthreadGroupName\tthreadId\tthreadName\tmessageId\tparentMessageId\trootMessageId\tsessionToken\n";
		checkTree(tree, expected);
		expected += "E2012-01-02 15:33:41.987\ttype\tname\t0\there is the data.\t\n";
		tree.setMessage(newEvent("type", "name", timestamp, "0", "here is the data."));
		checkTree(tree, expected);
	}
	@Test
	public void testTrace() {
		long timestamp = 1325489621987L;
		Trace trace = newTrace("type", "name", timestamp, "0", "here is the data.");
		check(trace, "L2012-01-02 15:33:41.987\ttype\tname\t0\there is the data.\t\n");
	}
	@Test
	public void testTraceForRawData() {
		long timestamp = 1325489621987L;
		String exception = "java.lang.Exception\n\tat com.dianping.cat.message.spi.codec.PlainTextMessageCodecTest.testTraceForException(PlainTextMessageCodecTest.java:112)\n";
		Trace trace = newTrace("Exception", Exception.class.getName(), timestamp, "ERROR", exception);
		check(trace,
		      "L2012-01-02 15:33:41.987\tException\tjava.lang.Exception\tERROR\t" + //
		            "java.lang.Exception\\n\\tat com.dianping.cat.message.spi.codec.PlainTextMessageCodecTest.testTraceForException(PlainTextMessageCodecTest.java:112)\\n\t\n");
	}
	@Test
	public void testTransactionNormal() {
		long timestamp = 1325489621987L;
		Transaction root = newTransaction("URL", "Review", timestamp, "0", 100, "/review/2468");
		root.addChild(newEvent("URL", "Payload", timestamp, "0", "ip=127.0.0.1&ua=Mozilla 5.0...&refer=...&..."));
		root.addChild(newTransaction("Service", "Auth", timestamp, "0", 20, "userId=1357&token=..."));
		root.addChild(newTransaction("Cache", "findReviewByPK", timestamp + 22, "Missing", 1, "2468") //
		      .addChild(newEvent("CacheHost", "host-1", timestamp + 22, "0", "ip=192.168.8.123")));
		root.addChild(newTransaction("DAL", "findReviewByPK", timestamp + 25, "0", 5, "select title,content from Review where id = ?"));
		root.addChild(newEvent("URL", "View", timestamp + 40, "0", "view=HTML"));
		check(root, "t2012-01-02 15:33:41.987\tURL\tReview\t\n" + //
		      "E2012-01-02 15:33:41.987\tURL\tPayload\t0\tip=127.0.0.1&ua=Mozilla 5.0...&refer=...&...\t\n" + //
		      "A2012-01-02 15:33:41.987\tService\tAuth\t0\t20000us\tuserId=1357&token=...\t\n" + //
		      "t2012-01-02 15:33:42.009\tCache\tfindReviewByPK\t\n" + //
		      "E2012-01-02 15:33:42.009\tCacheHost\thost-1\t0\tip=192.168.8.123\t\n" + //
		      "T2012-01-02 15:33:42.010\tCache\tfindReviewByPK\tMissing\t1000us\t2468\t\n" + //
		      "A2012-01-02 15:33:42.012\tDAL\tfindReviewByPK\t0\t5000us\tselect title,content from Review where id = ?\t\n" + //
		      "E2012-01-02 15:33:42.027\tURL\tView\t0\tview=HTML\t\n" + //
		      "T2012-01-02 15:33:42.087\tURL\tReview\t0\t100000us\t/review/2468\t\n");
	}
	@Test
	public void testTransactionSimple() {
		long timestamp = 1325489621987L;
		Transaction transaction = newTransaction("type", "name", timestamp, "0", 10, "here is the data.");
		check(transaction, "A2012-01-02 15:33:41.987\ttype\tname\t0\t10000us\there is the data.\t\n");
	}
}

+ 0 - 0
cat-client/src/test/java/com/dianping/cat/message/spi/codec/PlainTextMessageCodecTestConfigurator.java


Some files were not shown because too many files changed in this diff