Task has started,but nerver executed ,Anyone know what's problem there?
Now the Problem is every time i run the code it gives me only The output
Here's the final log
2020-11-02 17:03:30.852 DEBUG 9264 --- [nio-8089-exec-7] c.z.t.c.quartz.QuartzScheduleManager : addNewCronJob() called with: jobInfo = [JobInfo(className=class com.zx.tzgl.cronJob.YearPlanJob, identifier=25ddaab4-3e3b-45e9-84a3-77ff7ca4c049, groupName=zx, cornExpr=00 18 17 02 11 ? 2020, params={PLAY_YEAR_SWITCH_PARAMS={"planType":"1","isOpen":"1","enable":"1","annual":"2020至2025","closeTime":1604307810844,"id":"1"}})]
2020-11-02 17:03:30.852 INFO 9264 --- [nio-8089-exec-7] org.quartz.core.QuartzScheduler : Scheduler SchedulerFactory_$_NON_CLUSTERED started.
Here's my using quartz' version
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
Here's the quartz.properties file content
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 100
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 5000
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = myQuartzDB
org.quartz.dataSource.myQuartzDB.driver:oracle.jdbc.OracleDriver
#org.quartz.dataSource.myQuartzDB.URL:jdbc:oracle:thin:#//xx.xx.xx.xx:xxxx/cdb1
#org.quartz.dataSource.myQuartzDB.user:xxxx
#org.quartz.dataSource.myQuartzDB.password:xxxx
org.quartz.dataSource.myQuartzDB.URL:jdbc:oracle:thin:#//xx.xx.x.xx:xxxx/szorcl
org.quartz.dataSource.myQuartzDB.user:xxxx
org.quartz.dataSource.myQuartzDB.password:xxxx
org.quartz.dataSource.myQuartzDB.maxConnections:10
org.quartz.jobStore.isClustered=false
Here's Configuartion
#Configuration
public class QuartzConfig {
#Resource
private MyJobFactory myJobFactory;
#Bean(name = "SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(quartzProperties());
factory.setJobFactory(myJobFactory);
return factory;
}
#Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("config/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
#Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
#Bean(name = "quartzScheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
#Component
public class MyJobFactory extends AdaptableJobFactory {
#Autowired
private AutowireCapableBeanFactory capableBeanFactory;
#Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
Here's the schduler manager
#Component
#Slf4j
public class QuartzScheduleManager implements QuartzScheduleI {
#Resource(name = "quartzScheduler")
private Scheduler scheduler;
#Override #SneakyThrows
public void checkAndAddCornJob(JobInfo jobInfo) {
if (isExistJob(jobInfo)) {
resumeJob(jobInfo);
} else {
addNewCronJob(jobInfo);
}
}
#Override #SneakyThrows
public boolean isExistJob(JobInfo jobInfo) {
return scheduler.checkExists(new JobKey(jobInfo.getIdentifier(), jobInfo.getGroupName()));
}
#Override #SneakyThrows
public boolean isExistJob2(JobInfo jobInfo) {
boolean isExist = false;
JobKey jobKey1 = new JobKey(jobInfo.getIdentifier(), jobInfo.getGroupName());
List<? extends Trigger> triggers1 = scheduler.getTriggersOfJob(jobKey1);
if (triggers1.size() > 0) {
for (Trigger tg : triggers1) {
if ((tg instanceof CronTrigger) || (tg instanceof SimpleTrigger)) {
isExist = true;
}
}
}
return isExist;
}
private void addNewCronJob(JobInfo jobInfo) throws Exception {
scheduler.start();
//build job info
JobDetail jobDetail = JobBuilder.newJob(jobInfo.getClassName()).
withIdentity(jobInfo.getIdentifier(), jobInfo.getGroupName())
.build();
//add params to job
if (ObjectUtil.isNotNull(jobInfo.getParams()) && !jobInfo.getParams().isEmpty()) {
jobInfo.getParams().forEach((key, value) -> jobDetail.getJobDataMap().put(key, value));
}
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getCornExpr());
CronTrigger trigger = TriggerBuilder.newTrigger().
withIdentity(jobInfo.getIdentifier(), jobInfo.getGroupName())
.forJob(jobDetail)
.withSchedule(scheduleBuilder)
.build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new CustomException(ResultCode.PARAM_ERROR.getCode(), e.getMessage());
}
}
Here's the job listener
public class YearPlanJob implements Job {
#Override public void execute(JobExecutionContext jobExecutionContext) {
JobDetail jobDetail = jobExecutionContext.getJobDetail();
JobDataMap jobDataMap;
if (!jobDetail.getJobDataMap().isEmpty()) {
jobDataMap = jobDetail.getJobDataMap();
} else {
jobDataMap = jobExecutionContext.getTrigger().getJobDataMap();
}
}
}
Here's my test code
public String testQuartzTask() {
Date date = DateUtil.parse("2020-11-02 17:18:00", "yyyy-MM-dd HH:mm:ss");
PlanapplyYear planapplyYear = new PlanapplyYear();
planapplyYear.setCloseTime(new Date());
String jsonParams = JSONUtil.toJsonStr(planapplyYear);
JobInfo jobInfo = new JobInfo();
jobInfo.setClassName(YearPlanJob.class);
jobInfo.setIdentifier(UUID.randomUUID().toString());
jobInfo.setGroupName("zx");
jobInfo.setCornExpr(CronDateUtils.getCron(date));
jobInfo.setParams(ImmutableMap.of(PLAN_YEAR_SWITCH, jsonParams));
mQuartzScheduleManager.checkAndAddCornJob(jobInfo);
}
Can I somehow use doAnswer() when an exception is thrown?
I'm using this in my integration test to get method invocations and the test in configured the #RabbitListenerTest...
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyIT {
#Autowired
private RabbitTemplate rabbitTemplate;
#Autowired
private MyRabbitListener myRabbitListener;
#Autowired
private RabbitListenerTestHarness harness;
#Test
public void testListener() throws InterruptedException {
MyRabbitListener myRabbitListener = this.harness.getSpy("event");
assertNotNull(myRabbitListener);
final String message = "Test Message";
LatchCountDownAndCallRealMethodAnswer answer = new LatchCountDownAndCallRealMethodAnswer(1);
doAnswer(answer).when(myRabbitListener).event(message);
rabbitTemplate.convertAndSend("exchange", "key", message);
assertTrue(answer.getLatch().await(20, TimeUnit.SECONDS));
verify(myRabbitListener).messageReceiver(message);
}
#Configuration
#RabbitListenerTest
public static class Config {
#Bean
public MyRabbitListener myRabbitListener(){
return new MyRabbitListener();
}
}
}
It works ok but when I introduce an Exception being thrown, It doesn't i.e
This works
#RabbitListener(id = "event", queues = "queue-name")
public void event(String message) {
log.info("received message > " + message);
}
This doesn't
#RabbitListener(id = "event", queues = "queue-name")
public void event(String message) {
log.info("received message > " + message);
throw new ImmediateAcknowledgeAmqpException("Invalid message, " + message);
}
Any help appreciated
The LatchCountDownAndCallRealMethodAnswer is very basic
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
invocation.callRealMethod();
this.latch.countDown();
return null;
}
You can copy it to a new class and change it to something like
private volatile Exception exeption;
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
try {
invocation.callRealMethod();
}
catch (RuntimeException e) {
this.exception = e;
throw e;
}
finally {
this.latch.countDown();
}
return null;
}
public Exception getException() {
return this.exception;
}
then
assertTrue(answer.getLatch().await(20, TimeUnit.SECONDS));
assertThat(answer.getException(), isInstanceOf(ImmediateAcknowledgeAmqpException.class));
Please open a github issue; the framework should support this out-of-the-box.
I am using Apache Camel + SpringBoot + JDBC.I would like to do JUnit testing for end to end camel routes.My Processor is having DB Calls with Logic.But I am struggling to remove DB calls mocking to achieve testing.My junit is working but I dont know how to mock the JDBC object inside of my processor.I have lots of boiler plate code in my processors so I don't want to duplicate my logic inside of junits as I have done in this junit example.Please help me to solve this.
Applicaiton.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
MyRouter.java
#Component
public class MyRouter extends RouteBuilder {
#Autowired
OrderProcessor processor;
#Override
public void configure() throws Exception {
from("file://src/main/resources/input?delete=true&moveFailed=.error").routeId("route1").convertBodyTo(byte[].class)
.process(processor).split(simple("${body}")).to("file://src/main/resources/output?fileName=outputfile.txt&fileExist=Append");
}
}
OrderProcessor.java
#Component
public class OrderProcessor implements Processor{
#Autowired
DataSource dataSource;
#Autowired
OrderRepository repository;
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("process starts...");
byte[] data = exchange.getIn().getBody(byte[].class);
File dataFile = File.createTempFile("filepre", "bin");
FileUtils.writeByteArrayToFile(dataFile, data);
List<String> orders = new ArrayList<>();
FileReader fileReader = new FileReader(dataFile);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
line = line.substring(line.length()-1);
orders.add(repository.getOrder(line).toString()+"\n");
}
fileReader.close();
exchange.getIn().setBody(orders);
}
}
Order.java
public class Order {
private int id;
private int amount;
// getters and setters
#Override
public String toString() {
return this.id + ":" + this.amount;
}
}
OrderRepository.java
#Repository
public class OrderRepository {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
public Order getOrder(String id) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("refid", id);
Order extObj = jdbcTemplate.query("select * from world.orders where id = :refid",parameters, rs -> {
Order innerObj = null;
if (rs != null) {
while (rs.next()) {
innerObj = new Order();
innerObj.setId(rs.getInt("id"));
innerObj.setAmount(rs.getInt("amount"));
}
}
return innerObj;
});
return extObj;
}
}
jUnit Class:
public class MyRouterTest extends CamelSpringTestSupport {
#Override
protected AbstractApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("file://src/test/resources/input?delete=true&moveFailed=.error").routeId("route1")
.convertBodyTo(byte[].class).process(exchange -> {
Map<String, Integer> mockValues = new HashMap<>();
mockValues.put("1", 8);
mockValues.put("2", 10);
mockValues.put("3", 5);
System.out.println("process starts...");
byte[] data = exchange.getIn().getBody(byte[].class);
File dataFile = File.createTempFile("filepre", "bin");
FileUtils.writeByteArrayToFile(dataFile, data);
List<String> orders = new ArrayList<>();
FileReader fileReader = new FileReader(dataFile);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
line = line.substring(line.length() - 1);
Order orderTmp = new Order();
orderTmp.setId(Integer.parseInt(line.substring(line.length() - 1)));
orderTmp.setAmount(mockValues.get(line.substring(line.length() - 1)).intValue());
orders.add(orderTmp.toString() + "\n");
}
fileReader.close();
exchange.getIn().setBody(orders);
}).split(simple("${body}"))
.to("file://src/test/resources/output?fileName=outputfile.txt&fileExist=Append");
}
};
}
#Test
public void checkFileExistsInOutputDirectory() throws InterruptedException {
Thread.sleep(15000);
File file = new File("src/test/resources");
assertTrue(file.isDirectory());
// Check file content
}
}
inputfile.txt
line1
line2
line3
outputfile.txt
1:8
2:10
3:5
I think that you are trying to test too much, processor logic is not in the scope of MyRouter class, so you should use a mock. Tests for processor should be done separately.
My advice:
create mock of OrderProcessor
inject mock of OrderProcessor to real implementation of MyRouter
pass MyRouter instance (with mocked processor inside) to createRouteBuilder method in test
call route in tests
With Mockito it should be quite easy:
public class MyRouterTest extends CamelTestSupport {
#Mock
private OrderProcessor orderProcessor;
#InjectMocks
private MyRouter myRouter;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks( this );
super.setUp(); //important
doAnswer( invocation -> {
Exchange exchange = invocation.getArgumentAt( 0, Exchange.class );
//mock processor logic;
return null;
} ).when( orderProcessor ).process( any() );
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return myRouter;
}
#Test
public void testYourStuff() {
}
}
My project uses spring framework
WebSocketConfig.java
#Configuration
#EnableWebMvc
#EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(systemWebSocketHandler(),"/webSocketServer").addInterceptors(new WebSocketHandshakeInterceptor());
registry.addHandler(systemWebSocketHandler(), "/sockjs/webSocketServer").addInterceptors(new WebSocketHandshakeInterceptor())
.withSockJS();
}
#Bean
public WebSocketHandler systemWebSocketHandler(){
return new SystemWebSocketHandler();
}
}
SystemWebSocketHandler.java
public class SystemWebSocketHandler implements WebSocketHandler {
private static final Logger logger;
private static final ArrayList<WebSocketSession> users;
static {
users = new ArrayList<>();
logger = LoggerFactory.getLogger(SystemWebSocketHandler.class);
}
#Autowired
private WebSocketService webSocketService;
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.debug("connect to the websocket success......");
users.add(session);
String userName = (String) session.getAttributes().get(Constants.WEBSOCKET_USERNAME);
//查询未读消息
int count = webSocketService.getUnReadNews((String)session.getAttributes().get(Constants.WEBSOCKET_USERNAME));
session.sendMessage(new TextMessage(count+""));
}
#Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
}
#Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.debug("websocket connection closed......");
users.remove(session);
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
logger.debug("websocket connection closed......");
users.remove(session);
}
#Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 给所有在线用户发送消息
*
* #param message
*/
public void sendMessageToUsers(TextMessage message) {
for (WebSocketSession user : users) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 给某个用户发送消息
*
* #param userName
* #param message
*/
public void sendMessageToUser(String userName, TextMessage message) {
for (WebSocketSession user : users) {
if (user.getAttributes().get(Constants.WEBSOCKET_USERNAME).equals(userName)) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}
my jsp client
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/Origami/webSocketServer");
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080/Origami/webSocketServer");
} else {
websocket = new SockJS("http://localhost:8080/Origami/sockjs/webSocketServer");
}
this is my websocket code and it works well
now I want to send messages to the client in a controller ,this is my controller
#Controller
public class AdminController {
static Logger logger = LoggerFactory.getLogger(AdminController.class);
#Autowired(required = false)
private AdminService adminService;
#Autowired(required = false)
private SystemWebSocketHandler systemWebSocketHandler;
#RequestMapping("/auditing")
#ResponseBody
public String auditing(HttpServletRequest request){
String result = "fail";
int id = Integer.parseInt(request.getParameter("id"));
String reason = request.getParameter("reason");
String title = request.getParameter("title");
String username = request.getParameter("username");
News news = new News();
DateTime dateTime = DateTime.now();
news.setNewsTime(dateTime.toDate());
news.setState(0);
news.setUsername(username);
if(reason.equals("")){
result = adminService.auditingById(id,"Y");
news.setNewsContent(String.format(Constants.AUDIT_MESSAGE, username, title, reason));
adminService.addNewsWithUnAudit(news);
}else{
news.setNewsContent(String.format(Constants.UN_AUDIT_MESSAGE,username,title,reason));
result = adminService.addNewsWithUnAudit(news);
result = adminService.auditingById(id, "D");
}
//SystemServerEndPoint serverEndPoint = new SystemServerEndPoint();
int unReadNewsCount = adminService.getUnReadNews(username);
systemWebSocketHandler.sendMessageToUser(username, new TextMessage(unReadNewsCount + ""));
return result;
}
}
I want to call
systemWebSocketHandler.sendMessageToUser(username, new TextMessage(unReadNewsCount + ""));
to send message to the client but systemWebSocketHandler is null
How to inject the systemWebSocketHandler to the controller
or some other ideas to complete the required? Such as the server connect to the websocketserver when it need to send message to the client and closed when it finished
My English is poor, but I'm trying to learn
I have resolved the problem
#Controller
public class AdminController {
#Bean
public SystemWebSocketHandler systemWebSocketHandler() {
return new SystemWebSocketHandler();
}
I am writing a netty client and server application that will write JVM GC stats to a time-series database to analyse for around 300 servers. However my pipeline is throwing lots of these exceptions:
10/02/2012 10:14:23.415 New I/O server worker #2-2 ERROR GCEventsCollector - netty error
java.io.StreamCorruptedException: invalid type code: 6E
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
at java.io.ObjectInputStream.readSerialData(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at org.jboss.netty.handler.codec.serialization.ObjectDecoder.decode(ObjectDecoder.java:94)
at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:282)
at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:216)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:274)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:261)
at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:349)
at org.jboss.netty.channel.socket.nio.NioWorker.processSelectedKeys(NioWorker.java:280)
at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:200)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
It looks like it's creating an OutputStream but one already exists - so it throws that specific exception. It appears in my AIT environment where >300 servers are connecting but not in DEV where 1 agent is only connecting.
I suspect it is either a bug in the object decoder or I have incorrect code somewhere. Please could anyone explain why this happens?
Here is the collector:
public class GCEventsCollector extends AbstractDataCollector {
protected static final Logger logger = Logger.getLogger(GCEventsCollector.class);
private static final ExecutorService WORKERS = Executors.newCachedThreadPool();
private static final ChannelGroup GROUP = new DefaultChannelGroup("gc-events");
private final int port;
private final ServerBootstrap bootstrap;
public GCEventsCollector(final int port) {
logger.info("Creating GC Events collector on port " + port);
this.port = port;
this.bootstrap = newServerBootstrap(port);
}
/**
* Creates a bootstrap for creating bindings to sockets. Each channel has a pipeline, which contains the
* logic for handling a message such as encoding, decoding, buffering, etc.
*
* #param port port of socket
* #return configured bootstrap
*/
private ServerBootstrap newServerBootstrap(int port) {
ExecutorService bossExecutor = Executors.newCachedThreadPool();
ExecutorService workerExecutor = Executors.newCachedThreadPool();
NioServerSocketChannelFactory channelFactory =
new NioServerSocketChannelFactory(bossExecutor, workerExecutor);
ServerBootstrap bootstrap = new ServerBootstrap(channelFactory);
ChannelHandler collectorHandler = new CollectorHandler();
bootstrap.setPipelineFactory(new CollectorPipelineFactory(collectorHandler));
bootstrap.setOption("localAddress", new InetSocketAddress(port));
return bootstrap;
}
protected KDBCategory[] getKDBCategories() {
return new KDBCategory[] { KDBCategory.GC_EVENTS };
}
/**
* Bind to a socket to accept messages
*
* #throws Exception
*/
public void doStart() throws Exception {
Channel channel = bootstrap.bind();
GROUP.add(channel);
}
/**
* Disconnect the channel to stop accepting messages and wait until disconnected
*
* #throws Exception
*/
public void doStop() throws Exception {
logger.info("disconnecting");
GROUP.close().awaitUninterruptibly();
}
class CollectorHandler extends SimpleChannelHandler {
#Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
super.channelOpen(ctx, e);
GROUP.add(e.getChannel());
}
#Override
public void channelConnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
super.channelConnected(ctx, e);
logger.info("channel connected");
}
#Override
public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
super.channelDisconnected(ctx, e);
logger.info("channel disconnected");
}
#Override
public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Received GcStats event: " + e.toString());
}
WORKERS.execute(new Runnable() {
public void run() {
saveData(KDBCategory.GC_EVENTS, (GcEventsPersister) e.getMessage());
}
});
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
logger.error("netty error", e.getCause());
}
}
private static class CollectorPipelineFactory implements ChannelPipelineFactory {
private final ChannelHandler handler;
private CollectorPipelineFactory(ChannelHandler handler) {
this.handler = handler;
}
#Override
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new ObjectDecoder(), handler);
}
}
}
Here is the agent:
public class GCEventsAgent {
private final static Logger logger = Logger.getLogger(GCEventsAgent.class);
private static final ExecutorService bosses = Executors.newCachedThreadPool();
private static final ExecutorService workers = Executors.newCachedThreadPool();
private static final Timer timer = new HashedWheelTimer();
private static final String localHostName;
private static final ParseExceptionListener exceptionListener = new ExceptionListener();
static {
String name = "";
try {
name = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
logger.error("cannot retrieve local host name", e);
}
localHostName = name;
}
public static void main(final String[] args) {
checkArgument(args.length >= 3, "Usage: GCEventsAgent [log4j cfg] [mba cfg] [server cfg] [process 1] ... [process n]");
System.setProperty("log4j.configuration", "file:log4j.properties");
final String log4jConfig = args[0];
DOMConfigurator.configure(log4jConfig);
final String mbaConfig = args[1];
final String serverConfigPath = args[2];
final ServerConfig serverCfg = new ServerConfig(serverConfigPath);
setup(serverCfg, args);
}
private static void setup(ServerConfig cfg, String[] args) {
final String host = cfg.getParameter(String.class, "host");
final int port = cfg.getParameter(Integer.class, "port");
if (args.length == 3)
configurePolling(cfg, host, port);
else
configureProcesses(cfg, args, host, port);
}
private static void configureProcesses(final ServerConfig cfg,
final String[] args,
final String host,
final int port) {
final List<File> logFiles = logFiles(cfg, args);
logger.info("Initializing GC Agent for [" + host + ":" + port + "]");
final NioClientSocketChannelFactory channelFactory =
new NioClientSocketChannelFactory(bosses, workers);
final ClientBootstrap bootstrap = new ClientBootstrap(channelFactory);
bootstrap.setOption("remoteAddress", new InetSocketAddress(host, port));
final GCParserFactory parserFactory = new DefaultParserFactory();
final AgentProcessHandler agentHandler =
new AgentProcessHandler(bootstrap, logFiles, parserFactory);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
#Override
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new ObjectEncoder(), agentHandler);
}
});
bootstrap.connect().awaitUninterruptibly();
}
private static void configurePolling(ServerConfig cfg, String host, int port) {
final int frequency = cfg.getParameter(Integer.class, "frequency");
final NioClientSocketChannelFactory channelFactory =
new NioClientSocketChannelFactory(newCachedThreadPool(), newCachedThreadPool());
final ClientBootstrap bootstrap = new ClientBootstrap(channelFactory);
bootstrap.setOption("remoteAddress", new InetSocketAddress(host, port));
final GcParserSupplier parserSupplier = new GcParserSupplier();
final ConcurrentMap<File, Tailer> tailerMap = Maps.newConcurrentMap();
final ParseExceptionListener exceptionListener = new ExceptionListener();
final Set<File> discoveredSet = Sets.newHashSet();
final File directory = new File(cfg.getParameter(String.class, "logDirectory"));
final TailManager tailManager =
new TailManager(discoveredSet, tailerMap, parserSupplier, exceptionListener, localHostName);
final DetectionTask detectionTask = new DetectionTask(directory, discoveredSet, tailManager);
final FileWatcher fileWatcher =
new FileWatcher(Executors.newScheduledThreadPool(1), detectionTask, frequency);
final Timer timer = new HashedWheelTimer();
final EfxAgentHandler agentHandler =
new EfxAgentHandler(bootstrap, tailManager, fileWatcher, timer);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
#Override public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new ObjectEncoder(), agentHandler);
}
});
bootstrap.connect().awaitUninterruptibly();
}
private static List<File> logFiles(ServerConfig cfg, String[] args) {
String logDir = cfg.getParameter(String.class, "logDirectory");
final int n = args.length;
List<File> logFiles = new ArrayList<File>(n-3);
for (int i = 3; i < n; i++) {
String filePath = logDir + args[i] + ".gc.log";
try {
File file = new File(filePath);
if (!file.exists()) {
logger.info("missing log file so creating empty file at path: " + filePath);
File dir = file.getParentFile();
dir.mkdirs();
if (file.createNewFile())
logger.info("successfully created empty file at path: " + filePath);
}
logFiles.add(file);
} catch (IOException e) {
logger.error("error creating log file at path: " + filePath);
}
}
return logFiles;
}
static final class AgentPauseListener implements GCEventListener<CMSType, CMSHeap> {
private final Channel channel;
private final GcEventsPersister.Builder builder;
private byte state;
private AgentPauseListener(Channel channel,
GcEventsPersister.Builder builder) {
this.channel = channel;
this.builder = builder;
}
#Override
public void onPause(PauseDetail<CMSType> pauseDetail) {
logger.info("onPause");
checkState(state == 0x00 || state == 0x01);
builder
.collectionType(pauseDetail.getType() == null
? null : pauseDetail.getType().toString())
.start(new Instant(pauseDetail.getStartTimestamp()))
.end(new Instant(pauseDetail.getEndTimestamp()))
.pause(pauseDetail.getType() == null
? false : pauseDetail.getType().isPause())
.duration(pauseDetail.getPauseMillis());
if (state == 0x00)
channel.write(builder.build());
else
state |= 0x02;
}
#Override
public void onHeapBefore(HeapDetail<CMSHeap> details) {
logger.info("onHeapBefore");
checkState(state == 0x00);
builder.heapBefore(used(details)).heapBeforeTotal(total(details));
state |= 0x01;
}
#Override
public void onHeapAfter(HeapDetail<CMSHeap> details) {
logger.info("onHeapAfter");
checkState(state == 0x03);
builder.heapAfter(used(details)).heapAfterTotal(total(details));
channel.write(builder.build());
state = 0x00;
}
private final long used(HeapDetail<CMSHeap> details) {
return used(details, CMSHeap.PAR_NEW_GENERATION)
+ used(details, CMSHeap.CMS_GENERATION)
+ used(details, CMSHeap.CMS_PERM_GENERATION);
}
private final long used(HeapDetail<CMSHeap> heapDetail,
CMSHeap gen) {
final Map<CMSHeap, HeapDetail.HeapMetric> sizes = heapDetail.getSizes();
final long used = sizes.get(gen).getUsed();
logger.info("used = " + used);
return used;
}
private final long total(HeapDetail<CMSHeap> details) {
return total(details, CMSHeap.PAR_NEW_GENERATION)
+ total(details, CMSHeap.CMS_GENERATION)
+ total(details, CMSHeap.CMS_PERM_GENERATION);
}
private final long total(HeapDetail<CMSHeap> heapDetail,
CMSHeap gen) {
final Map<CMSHeap, HeapDetail.HeapMetric> sizes = heapDetail.getSizes();
return sizes.get(gen).getTotal();
}
}
static final class ExceptionListener implements ParseExceptionListener {
#Override
public void onParseError(int lineNo, String input, String error) {
logger.error("error parsing: " + lineNo + " - " + input + " - " + error);
}
}
static final class ReconnectTask implements TimerTask {
private final ClientBootstrap bootstrap;
ReconnectTask(ClientBootstrap bootstrap) {
this.bootstrap = bootstrap;
}
#Override
public void run(Timeout timeout) throws Exception {
bootstrap.connect();
}
}
static class AgentProcessHandler extends SimpleChannelHandler {
private final ClientBootstrap bootstrap;
private final List<File> logFiles;
private final GCParserFactory parserFactory;
private final Set<Tailer> tailers = new HashSet<Tailer>(4);
public AgentProcessHandler(ClientBootstrap bootstrap,
List<File> logFiles,
GCParserFactory parserFactory) {
this.bootstrap = bootstrap;
this.logFiles = logFiles;
this.parserFactory = parserFactory;
}
#Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
logger.info("channel connected");
for (File logFile : logFiles) {
logger.info("setting up scraper for logfile: " + logFile);
GCParser parser = parserFactory.getParser();
GcEventsPersister.Builder builder =
new GcEventsPersister.Builder(logFile.getName(), localHostName);
GCEventListener gcEventListener =
new AgentPauseListener(e.getChannel(), builder);
TailerListener listener =
new LogLineListener(parser, gcEventListener, exceptionListener);
Tailer tailer = Tailer.create(logFile, listener, 1000L, true);
tailers.add(tailer);
}
}
#Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
logger.error("channel disconnected");
stopTailers();
scheduleReconnect();
}
private void scheduleReconnect() {
timer.newTimeout(new ReconnectTask(bootstrap), 5L, TimeUnit.SECONDS);
}
private final void stopTailers() {
for (Tailer tailer : tailers) {
tailer.stop();
}
tailers.clear();
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
final Throwable cause = e.getCause();
logger.error(cause);
if (cause instanceof ConnectException) {
stopTailers();
scheduleReconnect();
}
}
};
private static class LogLineListener extends TailerListenerAdapter {
private final GCParser parser;
private final GCEventListener pauseListener;
private final ParseExceptionListener exceptionLister;
public LogLineListener(GCParser parser,
GCEventListener listener,
ParseExceptionListener exceptionLister) {
this.parser = parser;
this.pauseListener = listener;
this.exceptionLister = exceptionLister;
}
#Override
public void handle(String line) {
logger.info("handle(String line)");
parser.matchLine(line, pauseListener, exceptionLister);
}
}
private static final class GcParserSupplier
implements Supplier<GCParser<CMSType, CMSHeap>> {
#Override public GCParser<CMSType, CMSHeap> get() {
return new CMSParser();
}
}
private static final class TailManager implements FileHandler {
private final Set<File> discoveredSet;
private final ConcurrentMap<File, Tailer> tailerMap;
private final Supplier<GCParser<CMSType, CMSHeap>> parserSupplier;
private final ParseExceptionListener exceptionListener;
private final String host;
private volatile boolean go;
private TailManager(final Set<File> discoveredSet,
final ConcurrentMap<File, Tailer> tailerMap,
final Supplier<GCParser<CMSType, CMSHeap>> parserSupplier,
final ParseExceptionListener exceptionListener,
final String host) {
this.discoveredSet = discoveredSet;
this.tailerMap = tailerMap;
this.parserSupplier = parserSupplier;
this.exceptionListener = exceptionListener;
this.host = host;
}
public void stop() {
go = false;
for (Tailer tailer : tailerMap.values())
tailer.stop();
tailerMap.clear();
}
public void start() {
go = true;
}
#Override public void onNew(final File file,
final Channel channel) {
checkState(go);
GCParser<CMSType, CMSHeap> parser = parserSupplier.get();
String fileName = file.getName();
GcEventsPersister.Builder builder =
new GcEventsPersister.Builder(fileName, host);
AgentPauseListener eventListener =
new AgentPauseListener(channel, builder);
Function<Void, Void> removeTail = new Function<Void, Void>() {
#Override
public Void apply(#Nullable final Void input) {
final Tailer tailer = tailerMap.remove(file);
tailer.stop();
discoveredSet.remove(file);
return null;
}
};
GcTailAdaptor listener =
new GcTailAdaptor(logger, parser, eventListener, exceptionListener, removeTail);
tailerMap.put(file, Tailer.create(file, listener, 1000L, true));
}
#Override public void onDelete(File file, Channel channel) {
checkState(go);
final Tailer tailer = tailerMap.remove(file);
tailer.stop();
}
}
static class EfxAgentHandler extends SimpleChannelHandler {
private final ClientBootstrap bootstrap;
private final TailManager tailManager;
private final FileWatcher fileWatcher;
private final Timer timer;
public EfxAgentHandler(ClientBootstrap bootstrap,
TailManager tailManager,
FileWatcher fileWatcher,
Timer timer) {
this.bootstrap = bootstrap;
this.tailManager = tailManager;
this.fileWatcher = fileWatcher;
this.timer = timer;
}
#Override public void channelConnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
logger.info("channel connected");
tailManager.start();
fileWatcher.start(e.getChannel());
}
#Override public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
logger.error("channel disconnected");
tailManager.stop();
fileWatcher.stop();
scheduleReconnect();
}
private void scheduleReconnect() {
timer.newTimeout(new ReconnectTask(bootstrap), 5L, TimeUnit.SECONDS);
}
#Override public void exceptionCaught(ChannelHandlerContext ctx,
ExceptionEvent e) throws Exception {
final Throwable cause = e.getCause();
logger.error(cause);
if (cause instanceof ConnectException) {
tailManager.stop();
scheduleReconnect();
}
}
}
static final class GcTailAdaptor extends TailerListenerAdapter {
private final Logger logger;
private final GCParser parser;
private final GCEventListener eventListener;
private final ParseExceptionListener exceptionListener;
private final Function<Void, Void> removeTail;
private volatile long lastTail;
GcTailAdaptor(final Logger logger,
final GCParser parser,
final GCEventListener eventListener,
final ParseExceptionListener exceptionListener,
final Function<Void, Void> removeTail) {
this.logger = logger;
this.parser = parser;
this.eventListener = eventListener;
this.exceptionListener = exceptionListener;
this.removeTail = removeTail;
}
#Override public void handle(String line) {
lastTail();
parser.matchLine(line, eventListener, exceptionListener);
}
private final void lastTail() {
final long t = System.currentTimeMillis();
if (lastTail == 0L) {
lastTail = t;
return;
}
if ((t-lastTail)>=1800000L)
removeTail.apply(null);
}
#Override public void handle(Exception ex) {
logger.error(ex);
}
}
#VisibleForTesting
final static class DetectionTask implements Runnable {
private final File directory;
private final Set<File> discovered;
private final FileHandler handler;
private volatile Channel channel;
DetectionTask(final File directory,
final Set<File> discovered,
final FileHandler handler) {
this.discovered = discovered;
this.handler = handler;
this.directory = directory;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public boolean removeStaleFile(File file) {
checkArgument(discovered.contains(file),
"file is not discovered so cannot be stale");
return discovered.remove(file);
}
public void run() {
final File[] files = directory.listFiles();
for (int i=0, n=files.length; i<n; i++) {
final File file = files[i];
synchronized (discovered) {
if (!discovered.contains(file)) {
discovered.add(file);
handler.onNew(file, channel);
}
}
}
final ImmutableSet<File> logFiles = ImmutableSet.copyOf(files);
final ImmutableSet<File> diff = Sets.difference(discovered, logFiles).immutableCopy();
for (File file : diff) {
discovered.remove(file);
handler.onDelete(file, channel);
}
}
}
#VisibleForTesting static interface FileHandler {
void onNew(File file, Channel channel);
void onDelete(File file, Channel channel);
}
#VisibleForTesting
final static class FileWatcher {
private final ScheduledExecutorService executor;
private final DetectionTask detectionTask;
private final int frequency;
private volatile ScheduledFuture<?> task;
#VisibleForTesting
FileWatcher(ScheduledExecutorService executor,
DetectionTask detectionTask,
int frequency) {
this.executor = executor;
this.detectionTask = detectionTask;
this.frequency = frequency;
}
public void start(Channel chanel) {
task = executor.scheduleAtFixedRate(detectionTask, 0L, frequency, TimeUnit.SECONDS);
detectionTask.setChannel(chanel);
}
public void stop() {
task.cancel(true);
detectionTask.setChannel(null);
}
public static FileWatcher on(File directory,
FileHandler handler,
int frequency) {
checkNotNull(directory);
checkNotNull(handler);
checkArgument(directory.isDirectory(), "file is not a directory");
checkArgument(directory.canRead(), "no read access to directory");
checkArgument(0 < frequency, "frequency must be > 0");
final HashSet<File> objects = Sets.newHashSet();
final DetectionTask task = new DetectionTask(directory, objects, handler);
final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
return new FileWatcher(executorService, task, frequency);
}
}
}
I have incorrect code somewhere.
Correct. Specifically, you almost certainly have different ObjectInput/OutputStream lifetimes at server and client. Use the same streams for the life of the socket, and don't do any I/O over those sockets via any other means.
I discovered why this happens. I am using a deprecated ObjectDecoder that's not compatible with my client's ObjectEncoder. I am just sending a ByteBuffer instead and it's fine now.