/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2015 Pentaho Corporation. All rights reserved. */ package org.pentaho.telemetry; import java.io.File; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * The telemetry handler publishes telemetry events to a known location, so that developers can track usage/updates * of their work. * * Collected data for each telemetry event (described in {@link TelemetryEvent}) is stored in a handler queue. * A dedicated thread ({@link TelemetryEventKeeper}) reads this queue and stores the telemetry events in the filesystem. * At periodic intervals (default = once a day) another thread ({@link TelemetryEventSender}) reads these events from * the filesystem and publishes them to a remote endpoint. In case it succeeds, events are removed from the filesystem. * In case it fails, events are kept for a maximum of 5 days. After this time, events will be purged from the * filesystem. */ public class TelemetryHandler implements ITelemetryHandler { // region Constants private static final int EVENT_QUEUE_CAPACITY = 100; protected static final String DEFAULT_TELEMETRY_DIR_NAME = ".telemetry"; private static final String EVENT_KEEPER_THREAD_NAME = "Telemetry Event Keeper Thread"; private static final String EVENT_SENDER_THREAD_NAME = "Telemetry Event Sender Thread"; protected static final long DEFAULT_SEND_PERIOD_IN_MINUTES = 1440; // once a day // endregion // region Properties protected BlockingQueue<TelemetryEvent> getEventQueue() { return this.eventQueue; } protected void setEventQueue( BlockingQueue<TelemetryEvent> eventQueue ) { this.eventQueue = eventQueue; } private BlockingQueue<TelemetryEvent> eventQueue; public File getTelemetryDir() { return this.telemetryDir; } protected void setTelemetryDir( File telemetryDir ) { // ensure that the telemetry dir exists if ( !telemetryDir.exists() ) { telemetryDir.mkdir(); } this.telemetryDir = telemetryDir; } private File telemetryDir; public long getSendPeriodInMinutes() { return this.sendPeriodInMinutes; } protected void setSendPeriodInMinutes( long sendPeriodInMinutes ) { this.sendPeriodInMinutes = sendPeriodInMinutes; } private long sendPeriodInMinutes; protected Thread getEventKeeperThread() { return this.eventKeeperThread; } protected void setEventKeeperThread( Thread eventKeeperThread ) { this.eventKeeperThread = eventKeeperThread; } private Thread eventKeeperThread; protected ScheduledThreadPoolExecutor getEventSenderThreadPoolExecutor() { return eventSenderThreadPoolExecutor; } protected void setEventSenderThreadPoolExecutor( ScheduledThreadPoolExecutor eventSenderThreadPoolExecutor ) { this.eventSenderThreadPoolExecutor = eventSenderThreadPoolExecutor; } private ScheduledThreadPoolExecutor eventSenderThreadPoolExecutor; // endregion // region Constructors public TelemetryHandler() { this( DEFAULT_TELEMETRY_DIR_NAME, DEFAULT_SEND_PERIOD_IN_MINUTES ); } public TelemetryHandler( String telemetryDirPath, long sendPeriodInMinutes ) { // initialize the event queue this.setEventQueue( new ArrayBlockingQueue<TelemetryEvent>( EVENT_QUEUE_CAPACITY ) ); this.setTelemetryDir( new File( telemetryDirPath ) ); this.setSendPeriodInMinutes( sendPeriodInMinutes ); } /** * Called after class is instantiated by dependency injection */ public void init() { this.startEventKeeper(); this.startEventSender(); } /** * Called on object destruction by dependency injection */ public void destroy() { this.stopEventKeeper(); this.stopEventSender(); } // endregion // region Methods @Override public boolean queueEvent( TelemetryEvent event ) { BlockingQueue<TelemetryEvent> eventQueue = this.getEventQueue(); return eventQueue.offer( event ); } /** * Starts a dedicated thread ({@link TelemetryEventKeeper}) that reads the handler queue and stores the telemetry * events in the filesystem. */ private void startEventKeeper() { // create an event keeper that will store the events in the telemetry dir TelemetryEventKeeper eventKeeper = new TelemetryEventKeeper( this.getEventQueue(), this.getTelemetryDir() ); // create a thread to run the event keeper Thread thread = new Thread( eventKeeper ); thread.setName( EVENT_KEEPER_THREAD_NAME ); thread.setDaemon( true ); this.setEventKeeperThread( thread ); // start the thread thread.start(); } /** * Stops the event keeper thread. */ private void stopEventKeeper() { Thread thread = this.getEventKeeperThread(); thread.interrupt(); } /** * Starts a dedicated thread ({@link TelemetryEventSender}) that runs at periodic intervals, reads the events from * the filesystem and publishes them to a remote endpoint. */ private void startEventSender() { // create an event sender that will send the events from the telemetry dir to the remote endpoint TelemetryEventSender eventSender = new TelemetryEventSender( this.getTelemetryDir() ); // create a thread pool for the event sender ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor( 1, new ThreadFactory() { @Override public Thread newThread( Runnable r ) { Thread thread = new Thread( r ); thread.setName( EVENT_SENDER_THREAD_NAME ); thread.setDaemon( true ); return thread; } }, new ThreadPoolExecutor.DiscardPolicy() ); this.setEventSenderThreadPoolExecutor( threadPoolExecutor ); // schedule the event sender to run at periodic intervals threadPoolExecutor.scheduleAtFixedRate( eventSender, 0, this.getSendPeriodInMinutes(), TimeUnit.MINUTES ); } /** * Stops the event sender thread. */ private void stopEventSender() { // shutdown the event sender thread pool ScheduledThreadPoolExecutor threadPoolExecutor = this.getEventSenderThreadPoolExecutor(); threadPoolExecutor.shutdown(); } // endregion }