com.sparkred.mdex.metrics.publish.EndecaMDEXAgent.java Source code

Java tutorial

Introduction

Here is the source code for com.sparkred.mdex.metrics.publish.EndecaMDEXAgent.java

Source

package com.sparkred.mdex.metrics.publish;

import java.io.*;

import java.net.*;
import java.util.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.http.client.ClientProtocolException;

import com.newrelic.metrics.publish.Agent;
import com.newrelic.metrics.publish.util.Logger;
import com.sparkred.mdex.metrics.MdexStatsEntry;

import org.w3c.dom.*;
import org.xml.sax.SAXException;

/**
 * * Copyright 2014 Spark::red, LLC
 * 
 * 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. This is the impl of the New Relic Agent class that is the base
 * of the Endeca monitor
 * 
 * The basics are that it generates an http request to the configured host and
 * port and grabs the stats page located at /admin?op=stats. The result of this
 * "should" be well formed xml that is parsed into nodes, the results of which
 * are looped through and shoved into New Relic metrics that are pumped back to
 * the servers.
 * 
 * 
 * This initial impl reports on 4 root nodes on the status page : 1)
 * server_stats 2) resource_usage_stats 3) result_page_stats 4) navigation
 * 
 * @author Gordon Cooke
 * 
 */
public class EndecaMDEXAgent extends Agent {

    private static final Logger LOGGER = Logger.getLogger(EndecaMDEXAgent.class);

    private static final String METRIC_THRPT_10_SEC_AVG = "Throughput - 10 sec avg";

    private static final String METRIC_THRPT_1_MIN_AVG = "Throughput - 1 min avg";

    private static final String METRIC_THRPT_5_MIN_AVG = "Throughput - 5 min avg";

    private static final String UNIT_REQ_SEC = "req/sec";

    /**
     * As required by the Agent Class
     */
    private static final String GUID = "com.sparkred.EndecaPlugin";

    /**
     * As required by the Agent Class
     */
    private static final String VERSION = "0.1";

    /**
     * The host name to hit for the stats page
     */
    private String mHost;

    /**
     * the port the mdex engine is running on
     */
    private String mPort;

    /**
     * How to identify this particular agent uniquely
     */
    private String mAgentName;

    /**
     * combination of all the various pieces making up the actual URL that gets
     * called
     */
    private String mStatsUrl;

    /**
     * the path part of the URL
     */
    private String mStatsURLPath = "/admin?op=stats";

    /**
     * Allows for toggling the metrics off or on as needed
     */
    private boolean mReportMetrics = true;

    /**
     * The list of metrics that we care about being pulled, this maps to the
     * elements that wrap the stats elements
     */
    private static final String[] REPORTED_METRICS = new String[] { "server_stats", "resource_usage_stats",
            "result_page_stats", "navigation" };

    /**
     * Constructor allowing for the set up of the host and port. There is no
     * other data required.
     * 
     * @param pHost
     * @param pPort
     */
    public EndecaMDEXAgent(String pHost, String pPort) {
        super(GUID, VERSION);
        this.mHost = pHost;
        this.mPort = pPort;
        this.mAgentName = "MDEX - " + mHost + ":" + mPort;
        this.mStatsUrl = "http://" + mHost + ":" + mPort + mStatsURLPath;
    }

    public EndecaMDEXAgent(String pHost, String pPort, String pName) {
        super(GUID, VERSION);
        this.mHost = pHost;
        this.mPort = pPort;
        this.mAgentName = pName;
        this.mStatsUrl = "http://" + mHost + ":" + mPort + mStatsURLPath;
    }

    /**
     * Implementation of the pollCycle as per the required API This impl makes
     * the call to the server, grabs and parses the stats page and runs thorugh
     * the list of reported metrics as per the REPORTED_METRICS array and
     * returns all of the relevant data
     * 
     */
    @Override
    public void pollCycle() {
        try {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(new URL(mStatsUrl).openStream());
            for (int i = 0; i < REPORTED_METRICS.length; i++) {
                List<MdexStatsEntry> statsList = fetchMDEXStatsByType(doc, REPORTED_METRICS[i]);
                reportMetrics(statsList);
            }
            // throughput stats have a different format
            reportThroughputMetrics(doc);

        } catch (ClientProtocolException e) {
            LOGGER.error(e, e.getMessage());
        } catch (ConnectException ce) {
            LOGGER.error(ce,
                    "Cannot connect to the provided URL, please validate your configs and make sure the host/port is accesible");
        } catch (IOException e) {
            LOGGER.error(e, e.getMessage());
        } catch (ParserConfigurationException e) {
            LOGGER.error(e, e.getMessage());
        } catch (SAXException e) {
            LOGGER.error(e, e.getMessage());
        }

    }

    /**
     * sample XML : <throughput units="req/sec" five_minute_avg="0.02"
     * one_minute_avg="0.06" ten_second_avg="0.07" />
     * 
     * 
     * @param pDoc
     */
    private void reportThroughputMetrics(Document pDoc) {
        NodeList thorughPut_stats = pDoc.getElementsByTagName("throughput");
        Node node = thorughPut_stats.item(0);
        NamedNodeMap attr = node.getAttributes();
        Double fiveMinuteAvg = Double.valueOf(attr.getNamedItem("five_minute_avg").getNodeValue());
        Double oneMinuteAvg = Double.valueOf(attr.getNamedItem("one_minute_avg").getNodeValue());
        Double tenSecondAvg = Double.valueOf(attr.getNamedItem("ten_second_avg").getNodeValue());

        reportMetric(METRIC_THRPT_5_MIN_AVG, UNIT_REQ_SEC, fiveMinuteAvg);
        reportMetric(METRIC_THRPT_1_MIN_AVG, UNIT_REQ_SEC, oneMinuteAvg);
        reportMetric(METRIC_THRPT_10_SEC_AVG, UNIT_REQ_SEC, tenSecondAvg);
    }

    /**
     * Takes in the XML doc returned on the stats page and parses it out into a
     * list of MdexStatsEntry items
     * 
     * @param pDoc
     *            - the xml doc to pull data from
     * @param pRootNode
     *            - the name of the node from which metrics should be pulled
     * @return - the list of items with the relevant metrics
     * @throws IOException
     * @throws ClientProtocolException
     */
    private List<MdexStatsEntry> fetchMDEXStatsByType(Document pDoc, String pRootNode)
            throws IOException, ClientProtocolException {
        List<MdexStatsEntry> stats_entries = new ArrayList<MdexStatsEntry>();

        NodeList server_stats = pDoc.getElementsByTagName(pRootNode);
        NodeList statsList = server_stats.item(0).getChildNodes();
        for (int i = 0; i < statsList.getLength(); i++) {
            Node node = statsList.item(i);
            if (node.getNodeName().equals(MdexStatsEntry.STAT_NODE_NAME)) {
                NamedNodeMap attr = node.getAttributes();
                MdexStatsEntry entry = new MdexStatsEntry(
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_NAME).getNodeValue(),
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_UNITS).getNodeValue(),
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_COUNT).getNodeValue(),
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_AVG).getNodeValue(),
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_STDDEV).getNodeValue(),
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_MIN).getNodeValue(),
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_MAX).getNodeValue(),
                        attr.getNamedItem(MdexStatsEntry.METRIC_NODE_TOTAL).getNodeValue());
                stats_entries.add(entry);
            }
        }
        return stats_entries;
    }

    /**
     * Send the metrics to New Relic. For every entry there are 5 metrics sent
     * the name is pulled from the entry name the units pulled from the units
     * the actual Metrics are Count, Total, Average, Min, Max
     * 
     * @param pEntryList
     *            - the list of MdexStatsEntrys to be looped through an report
     *            metrics on
     */
    private void reportMetrics(List<MdexStatsEntry> pEntryList) {
        for (Iterator<MdexStatsEntry> iterator = pEntryList.iterator(); iterator.hasNext();) {
            MdexStatsEntry mdexStatsEntry = (MdexStatsEntry) iterator.next();
            reportMetric(mdexStatsEntry.getName() + " - Count", mdexStatsEntry.getUnits(),
                    mdexStatsEntry.getCount());
            reportMetric(mdexStatsEntry.getName() + " - Total", mdexStatsEntry.getUnits(),
                    mdexStatsEntry.getTotal());
            reportMetric(mdexStatsEntry.getName() + " - Average", mdexStatsEntry.getUnits(),
                    mdexStatsEntry.getAverage());
            reportMetric(mdexStatsEntry.getName() + " - Min", mdexStatsEntry.getUnits(), mdexStatsEntry.getMin());
            reportMetric(mdexStatsEntry.getName() + " - Max", mdexStatsEntry.getUnits(), mdexStatsEntry.getMax());
        }
    }

    @Override
    public String getAgentName() {
        return mAgentName;
    }

    public boolean isReportMetrics() {
        return mReportMetrics;
    }

    protected void setReportMetrics(boolean pReportMetrics) {
        mReportMetrics = pReportMetrics;
    }
}