Java tutorial
/* * Jitsi Videobridge, OpenSource video conferencing. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jitsi.videobridge.influxdb; import org.ice4j.ice.*; import org.jitsi.service.configuration.*; import org.jitsi.service.neomedia.*; import org.jitsi.util.*; import org.jitsi.videobridge.*; import org.jitsi.videobridge.eventadmin.*; import org.json.simple.*; import java.io.*; import java.net.*; import java.util.*; import java.util.concurrent.*; /** * Allows logging of {@link InfluxDBEvent}s using an * <tt>InfluxDB</tt> instance. * * @author Boris Grozev * @author George Politis */ public class LoggingHandler implements EventHandler { /** * The names of the columns of a "conference created" event. */ private static final String[] CONFERENCE_CREATED_COLUMNS = new String[] { "conference_id", "focus" }; /** * The names of the columns of a "conference expired" event. */ private static final String[] CONFERENCE_EXPIRED_COLUMNS = new String[] { "conference_id" }; /** * The names of the columns of a "content created" event. */ private static final String[] CONTENT_CREATED_COLUMNS = new String[] { "name", "conference_id" }; /** * The names of the columns of a "content expired" event. */ private static final String[] CONTENT_EXPIRED_COLUMNS = new String[] { "name", "conference_id" }; /** * The names of the columns of a "channel created" event. */ private static final String[] CHANNEL_CREATED_COLUMNS = new String[] { "channel_id", "content_name", "conference_id", "endpoint_id", "lastn" }; /** * The names of the columns of a "rtp channel expired" event. */ private static final String[] RTP_CHANNEL_EXPIRED_COLUMNS = new String[] { "channel_id", "content_name", "conference_id", "stats.local_ip", "stats.local_port", "stats.remote_ip", "stats.remote_port", "stats.nb_received_bytes", "stats.nb_sent_bytes", "stats.nb_packets", "stats.nb_packets_lost", }; /** * The names of the columns of a "channel expired" event. */ private static final String[] CHANNEL_EXPIRED_COLUMNS = new String[] { "channel_id", "content_name", "conference_id", }; /** * The names of the columns of a "transport created" event. */ private static final String[] TRANSPORT_CREATED_COLUMNS = new String[] { "hash_code", "conference_id", "num_components", "ufrag", "is_controlling" }; /** * The names of the columns of a "transport manager channel added" event. */ private static final String[] TRANSPORT_CHANNEL_ADDED_COLUMNS = new String[] { "hash_code", "conference_id", "channel_id", }; /** * The names of the columns of a "transport manager channel removed" event. */ private static final String[] TRANSPORT_CHANNEL_REMOVED_COLUMNS = new String[] { "hash_code", "conference_id", "channel_id", }; /** * The names of the columns of a "transport manager connected" event. */ private static final String[] TRANSPORT_CONNECTED_COLUMNS = new String[] { "hash_code", "conference_id", "selected_pairs" }; /** * The names of the columns of a "transport manager connected" event. */ private static final String[] TRANSPORT_STATE_CHANGED_COLUMNS = new String[] { "hash_code", "conference_id", "old_state", "new_state" }; /** * The names of the columns of an "endpoint created" event. */ private static final String[] ENDPOINT_CREATED_COLUMNS = new String[] { "conference_id", "endpoint_id", }; /** * The names of the columns of an "endpoint display name" event. */ private static final String[] ENDPOINT_DISPLAY_NAME_COLUMNS = new String[] { "conference_id", "endpoint_id", "display_name" }; /** * The name of the property which specifies whether logging to an * <tt>InfluxDB</tt> is enabled. */ public static final String ENABLED_PNAME = "org.jitsi.videobridge.log.INFLUX_DB_ENABLED"; /** * The name of the property which specifies the protocol, hostname and * port number (in URL format) to use to connect to <tt>InfluxDB</tt>. */ public static final String URL_BASE_PNAME = "org.jitsi.videobridge.log.INFLUX_URL_BASE"; /** * The name of the property which specifies the name of the * <tt>InfluxDB</tt> database. */ public static final String DATABASE_PNAME = "org.jitsi.videobridge.log.INFLUX_DATABASE"; /** * The name of the property which specifies the username to use to connect * to <tt>InfluxDB</tt>. */ public static final String USER_PNAME = "org.jitsi.videobridge.log.INFLUX_USER"; /** * The name of the property which specifies the password to use to connect * to <tt>InfluxDB</tt>. */ public static final String PASS_PNAME = "org.jitsi.videobridge.log.INFLUX_PASS"; /** * The <tt>Logger</tt> used by the <tt>LoggingHandler</tt> class * and its instances to print debug information. */ private static final Logger logger = Logger.getLogger(LoggingHandler.class); /** * The <tt>Executor</tt> which is to perform the task of sending data to * <tt>InfluxDB</tt>. */ private final Executor executor = ExecutorUtils.newCachedThreadPool(true, LoggingHandler.class.getName()); /** * The <tt>URL</tt> to be used to POST to <tt>InfluxDB</tt>. Besides the * protocol, host and port also encodes the database name, user name and * password. */ private final URL url; /** * Initializes a new <tt>LoggingHandler</tt> instance, by reading * its configuration from <tt>cfg</tt>. * @param cfg the <tt>ConfigurationService</tt> to use. * * @throws Exception if initialization fails */ public LoggingHandler(ConfigurationService cfg) throws Exception { if (cfg == null) throw new NullPointerException("cfg"); String s = "Required property not set: "; String urlBase = cfg.getString(URL_BASE_PNAME, null); if (urlBase == null) throw new Exception(s + URL_BASE_PNAME); String database = cfg.getString(DATABASE_PNAME, null); if (database == null) throw new Exception(s + DATABASE_PNAME); String user = cfg.getString(USER_PNAME, null); if (user == null) throw new Exception(s + USER_PNAME); String pass = cfg.getString(PASS_PNAME, null); if (pass == null) throw new Exception(s + PASS_PNAME); String urlStr = urlBase + "/db/" + database + "/series?u=" + user + "&p=" + pass; url = new URL(urlStr); logger.info("Initialized InfluxDBLoggingService for " + urlBase + ", database \"" + database + "\""); } /** * Logs an <tt>InfluxDBEvent</tt> to an <tt>InfluxDB</tt> database. This * method returns without blocking, the blocking operations are performed * by a thread from {@link #executor}. * * @param e the <tt>Event</tt> to log. */ @SuppressWarnings("unchecked") protected void logEvent(InfluxDBEvent e) { // The following is a sample JSON message in the format used by InfluxDB // [ // { // "name": "series_name", // "columns": ["column1", "column2"], // "points": [ // ["value1", 1234], // ["value2", 5678] // ] // } // ] boolean useLocalTime = e.useLocalTime(); long now = System.currentTimeMillis(); boolean multipoint = false; int pointCount = 1; JSONArray columns = new JSONArray(); JSONArray points = new JSONArray(); Object[] values = e.getValues(); if (useLocalTime) columns.add("time"); Collections.addAll(columns, e.getColumns()); if (values[0] instanceof Object[]) { multipoint = true; pointCount = values.length; } if (multipoint) { for (int i = 0; i < pointCount; i++) { if (!(values[i] instanceof Object[])) continue; JSONArray point = new JSONArray(); if (useLocalTime) point.add(now); Collections.addAll(point, (Object[]) values[i]); points.add(point); } } else { JSONArray point = new JSONArray(); if (useLocalTime) point.add(now); Collections.addAll(point, values); points.add(point); } JSONObject jsonObject = new JSONObject(); jsonObject.put("name", e.getName()); jsonObject.put("columns", columns); jsonObject.put("points", points); JSONArray jsonArray = new JSONArray(); jsonArray.add(jsonObject); // TODO: this is probably a good place to optimize by grouping multiple // events in a single POST message and/or multiple points for events // of the same type together). final String jsonString = jsonArray.toJSONString(); executor.execute(new Runnable() { @Override public void run() { sendPost(jsonString); } }); } /** * Sends the string <tt>s</tt> as the contents of an HTTP POST request to * {@link #url}. * @param s the content of the POST request. */ private void sendPost(final String s) { try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-type", "application/json"); connection.setDoOutput(true); DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); outputStream.writeBytes(s); outputStream.flush(); outputStream.close(); int responseCode = connection.getResponseCode(); if (responseCode != 200) throw new IOException("HTTP response code: " + responseCode); } catch (IOException ioe) { logger.info("Failed to post to influxdb: " + ioe); } } /** * * @param conference */ private void conferenceCreated(Conference conference) { if (conference == null) { logger.debug("Could not log conference created event because " + "the conference is null."); return; } String focus = conference.getFocus(); logEvent(new InfluxDBEvent("conference_created", CONFERENCE_CREATED_COLUMNS, new Object[] { conference.getID(), focus != null ? focus : "null" })); } /** * * @param conference */ private void conferenceExpired(Conference conference) { if (conference == null) { logger.debug("Could not log conference expired event because " + "the conference is null."); return; } logEvent(new InfluxDBEvent("conference_expired", CONFERENCE_EXPIRED_COLUMNS, new Object[] { conference.getID() })); } /** * * @param endpoint */ private void endpointCreated(Endpoint endpoint) { if (endpoint == null) { logger.debug("Could not log endpoint created event because " + "the endpoint is null."); return; } Conference conference = endpoint.getConference(); if (conference == null) { logger.debug("Could not log endpoint created event because " + "the conference is null."); return; } logEvent(new InfluxDBEvent("endpoint_created", ENDPOINT_CREATED_COLUMNS, new Object[] { conference.getID(), endpoint.getID() })); } /** * * @param endpoint */ private void endpointDisplayNameChanged(Endpoint endpoint) { if (endpoint == null) { logger.debug("Could not log endpoint display name changed" + " event because the endpoint is null."); return; } Conference conference = endpoint.getConference(); if (conference == null) { logger.debug("Could not log endpoint display name changed " + " event because the conference is null."); return; } logEvent(new InfluxDBEvent("endpoint_display_name", ENDPOINT_DISPLAY_NAME_COLUMNS, new Object[] { conference.getID(), endpoint.getID(), endpoint.getDisplayName() })); } /** * * @param content */ private void contentCreated(Content content) { if (content == null) { logger.debug("Could not log content created event because " + "the content is null."); return; } Conference conference = content.getConference(); if (conference == null) { logger.debug("Could not log content created event because " + "the conference is null."); return; } logEvent(new InfluxDBEvent("content_created", CONTENT_CREATED_COLUMNS, new Object[] { content.getName(), conference.getID() })); } /** * * @param content */ private void contentExpired(Content content) { if (content == null) { logger.debug("Could not log content expired event because " + "the content is null."); return; } Conference conference = content.getConference(); if (conference == null) { logger.debug("Could not log content expired event because " + "the conference is null."); return; } logEvent(new InfluxDBEvent("content_expired", CONTENT_EXPIRED_COLUMNS, new Object[] { content.getName(), conference.getID() })); } private void transportChannelAdded(Channel channel) { TransportManager transportManager; try { transportManager = channel.getTransportManager(); } catch (IOException e) { logger.error("Could not log the transport channel added event " + "because of an error.", e); return; } Content content = channel.getContent(); if (content == null) { logger.debug("Could not log the transport channel added event " + "because the content is null."); return; } Conference conference = content.getConference(); if (conference == null) { logger.debug("Could not log the transport channel added event " + "because the conference is null."); return; } logEvent(new InfluxDBEvent("transport_channel_added", TRANSPORT_CHANNEL_ADDED_COLUMNS, new Object[] { String.valueOf(transportManager.hashCode()), conference.getID(), channel.getID() })); } /** * * @param channel */ private void transportChannelRemoved(Channel channel) { TransportManager transportManager; try { transportManager = channel.getTransportManager(); } catch (IOException e) { logger.error("Could not log the transport channel removed event " + "because of an error.", e); return; } Content content = channel.getContent(); if (content == null) { logger.debug("Could not log the transport channel removed event " + "because the content is null."); return; } Conference conference = content.getConference(); if (conference == null) { logger.debug("Could not log the transport channel removed event " + "because the conference is null."); return; } logEvent(new InfluxDBEvent("transport_channel_removed", TRANSPORT_CHANNEL_REMOVED_COLUMNS, new Object[] { String.valueOf(transportManager.hashCode()), conference.getID(), channel.getID() })); } /** * * @param transportManager * @param oldState * @param newState */ private void transportStateChanged(IceUdpTransportManager transportManager, IceProcessingState oldState, IceProcessingState newState) { Conference conference = transportManager.getConference(); if (conference == null) { logger.debug("Could not log the transport state changed event " + "because the conference is null."); return; } logEvent(new InfluxDBEvent("transport_state_changed", TRANSPORT_STATE_CHANGED_COLUMNS, new Object[] { String.valueOf(transportManager.hashCode()), conference.getID(), oldState == null ? "null" : oldState.toString(), newState == null ? "null" : newState.toString() })); } /** * * @param transportManager */ private void transportCreated(IceUdpTransportManager transportManager) { Conference conference = transportManager.getConference(); if (conference == null) { logger.debug("Could not log the transport created event " + "because the conference is null."); return; } Agent agent = transportManager.getAgent(); if (agent == null) { logger.debug("Could not log the transport created event " + "because the agent is null."); return; } logEvent(new InfluxDBEvent("transport_created", TRANSPORT_CREATED_COLUMNS, new Object[] { String.valueOf(transportManager.hashCode()), conference.getID(), transportManager.getNumComponents(), agent.getLocalUfrag(), Boolean.valueOf(transportManager.isControlling()).toString() })); } /** * * @param transportManager */ private void transportConnected(IceUdpTransportManager transportManager) { Conference conference = transportManager.getConference(); if (conference == null) { logger.debug("Could not log the transport connected event " + "because the conference is null."); return; } IceMediaStream iceStream = transportManager.getIceStream(); if (iceStream == null) { logger.debug("Could not log the transport connected event " + "because the iceStream is null."); return; } StringBuilder s = new StringBuilder(); for (Component component : iceStream.getComponents()) { CandidatePair pair = component.getSelectedPair(); s.append(pair.getLocalCandidate().getTransportAddress()).append(" -> ") .append(pair.getRemoteCandidate().getTransportAddress()).append("; "); } logEvent(new InfluxDBEvent("transport_connected", TRANSPORT_CONNECTED_COLUMNS, new Object[] { String.valueOf(transportManager.hashCode()), conference.getID(), s.toString() })); } /** * * @param channel */ private void channelCreated(Channel channel) { if (channel == null) { logger.debug("Could not log the channel created event " + "because the channel is null."); return; } Content content = channel.getContent(); if (content == null) { logger.debug("Could not log the channel created event " + "because the content is null."); return; } Conference conference = content.getConference(); if (conference == null) { logger.debug("Could not log the channel created event " + "because the conference is null."); return; } String endpointID = ""; Endpoint endpoint = channel.getEndpoint(); if (endpoint != null) { endpointID = endpoint.getID(); } int lastN = -1; if (channel instanceof VideoChannel) { lastN = ((VideoChannel) channel).getLastN(); } logEvent(new InfluxDBEvent("channel_created", CHANNEL_CREATED_COLUMNS, new Object[] { channel.getID(), content.getName(), conference.getID(), endpointID, lastN })); } /** * * @param channel */ private void channelExpired(Channel channel) { if (channel == null) { logger.debug("Could not log the channel expired event " + "because the channel is null."); return; } Content content = channel.getContent(); if (content == null) { logger.debug("Could not log the channel expired event " + "because the content is null."); return; } Conference conference = content.getConference(); if (conference == null) { logger.debug("Could not log the channel expired event " + "because the conference is null."); return; } MediaStreamStats stats = null; if (channel instanceof RtpChannel) { RtpChannel rtpChannel = (RtpChannel) channel; MediaStream stream = rtpChannel.getStream(); if (stream != null) { stats = stream.getMediaStreamStats(); } } if (stats != null) { Object[] values = new Object[] { channel.getID(), content.getName(), conference.getID(), stats.getLocalIPAddress(), stats.getLocalPort(), stats.getRemoteIPAddress(), stats.getRemotePort(), stats.getNbReceivedBytes(), stats.getNbSentBytes(), stats.getNbPackets(), stats.getNbPacketsLost(), }; logEvent(new InfluxDBEvent("channel_expired", RTP_CHANNEL_EXPIRED_COLUMNS, values)); } else { Object[] values = new Object[] { channel.getID(), content.getName(), conference.getID(), }; logEvent(new InfluxDBEvent("channel_expired", CHANNEL_EXPIRED_COLUMNS, values)); } } @Override public void handleEvent(Event event) { if (event == null) { logger.debug("Could not handle the event because it was null."); return; } String topic = event.getTopic(); if (EventFactory.CHANNEL_CREATED_TOPIC.equals(topic)) { Channel channel = (Channel) event.getProperty(EventFactory.EVENT_SOURCE); channelCreated(channel); } else if (EventFactory.CHANNEL_EXPIRED_TOPIC.equals(topic)) { Channel channel = (Channel) event.getProperty(EventFactory.EVENT_SOURCE); channelExpired(channel); } else if (EventFactory.CONFERENCE_CREATED_TOPIC.equals(topic)) { Conference conference = (Conference) event.getProperty(EventFactory.EVENT_SOURCE); conferenceCreated(conference); } else if (EventFactory.CONFERENCE_EXPIRED_TOPIC.equals(topic)) { Conference conference = (Conference) event.getProperty(EventFactory.EVENT_SOURCE); conferenceExpired(conference); } else if (EventFactory.CONTENT_CREATED_TOPIC.equals(topic)) { Content content = (Content) event.getProperty(EventFactory.EVENT_SOURCE); contentCreated(content); } else if (EventFactory.CONTENT_EXPIRED_TOPIC.equals(topic)) { Content content = (Content) event.getProperty(EventFactory.EVENT_SOURCE); contentExpired(content); } else if (EventFactory.ENDPOINT_CREATED_TOPIC.equals(topic)) { Endpoint endpoint = (Endpoint) event.getProperty(EventFactory.EVENT_SOURCE); endpointCreated(endpoint); } else if (EventFactory.ENDPOINT_DISPLAY_NAME_CHANGED_TOPIC.equals(topic)) { Endpoint endpoint = (Endpoint) event.getProperty(EventFactory.EVENT_SOURCE); endpointDisplayNameChanged(endpoint); } else if (EventFactory.TRANSPORT_CHANNEL_ADDED_TOPIC.equals(topic)) { Channel channel = (Channel) event.getProperty(EventFactory.EVENT_SOURCE); transportChannelAdded(channel); } else if (EventFactory.TRANSPORT_CHANNEL_REMOVED_TOPIC.equals(topic)) { Channel channel = (Channel) event.getProperty(EventFactory.EVENT_SOURCE); transportChannelRemoved(channel); } else if (EventFactory.TRANSPORT_CONNECTED_TOPIC.equals(topic)) { IceUdpTransportManager transportManager = (IceUdpTransportManager) event .getProperty(EventFactory.EVENT_SOURCE); transportConnected(transportManager); } else if (EventFactory.TRANSPORT_CREATED_TOPIC.equals(topic)) { IceUdpTransportManager transportManager = (IceUdpTransportManager) event .getProperty(EventFactory.EVENT_SOURCE); transportCreated(transportManager); } else if (EventFactory.TRANSPORT_STATE_CHANGED_TOPIC.equals(topic)) { IceUdpTransportManager transportManager = (IceUdpTransportManager) event .getProperty(EventFactory.EVENT_SOURCE); IceProcessingState oldState = (IceProcessingState) event.getProperty("oldState"); IceProcessingState newState = (IceProcessingState) event.getProperty("newState"); transportStateChanged(transportManager, oldState, newState); } } }