com.mfizz.observer.core.ServiceObserver.java Source code

Java tutorial

Introduction

Here is the source code for com.mfizz.observer.core.ServiceObserver.java

Source

package com.mfizz.observer.core;

/*
 * #%L
 * mfizz-observer-core
 * %%
 * Copyright (C) 2012 mfizz
 * %%
 * 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.
 * #L%
 */

import com.mfizz.observer.common.SummaryPeriod;
import com.mfizz.util.TimePeriod;
import com.mfizz.util.TimeSeries;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.MetricName;
import com.yammer.metrics.core.Timer;
import com.yammer.metrics.core.TimerContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.HttpContext;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author joe@mfizz.com
 */
public class ServiceObserver<D extends Delta, A extends Aggregate, S extends Summary> {
    private static final Logger logger = LoggerFactory.getLogger(ServiceObserver.class);

    static public final String DEFAULT_GROUP = "all";

    private final ServiceObserverConfiguration serviceConfig;
    private final ExecutorService executor;
    private final ConcurrentHashMap<String, Observer<D>> observers;
    private final Class<D> dataClass;
    private final Class<A> aggregateClass;
    private final Class<S> summaryClass;
    private final ContentParser<D> contentParser;
    private final ObserverNamingStrategy ons;
    // httpclient used between all observers
    private final DefaultHttpClient httpclient;
    // counters & metrics
    private final Timer snapshotAllTimer;
    private final AtomicLong snapshotAllAttemptedCounter;
    private final AtomicLong snapshotAllCompletedCounter;
    private final AtomicReference<SnapshotAllResult> lastSnapshotAllResult;

    //private final AtomicLong lastSnapshotAllTimestamp;
    //private final AtomicInteger lastSnapshotsCompletedCounter;
    //private final AtomicInteger lastSnapshotsFailedCounter;

    // atomic references to prepared instantaneous/1/5/15 min summaries for each group
    private final TreeSet<String> groups;
    private final ConcurrentHashMap<String, SummaryGroup<S>> summary;
    private final ConcurrentHashMap<String, TimeSeries<ObserveAggregateSnapshot<A>>> snapshots;

    public ServiceObserver(ServiceObserverConfiguration serviceConfig, ExecutorService executor, Class<D> dataClass,
            Class<A> aggregateClass, Class<S> summaryClass, ContentParser<D> contentParser) {
        this(serviceConfig, executor, dataClass, aggregateClass, summaryClass, contentParser, null);
    }

    public ServiceObserver(ServiceObserverConfiguration serviceConfig, ExecutorService executor, Class<D> dataClass,
            Class<A> aggregateClass, Class<S> summaryClass, ContentParser<D> contentParser,
            ObserverNamingStrategy ons) {
        this.serviceConfig = serviceConfig;
        this.executor = executor;
        this.observers = new ConcurrentHashMap<String, Observer<D>>();
        this.dataClass = dataClass;
        this.aggregateClass = aggregateClass;
        this.summaryClass = summaryClass;
        this.contentParser = contentParser;
        if (ons == null) {
            this.ons = ObserverNamingStrategy.DEFAULT;
        } else {
            this.ons = ons;
        }
        this.snapshotAllTimer = Metrics.newTimer(
                new MetricName("com.mfizz.observer.ServiceObservers", serviceConfig.getName(), "snapshot-all-time"),
                TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS);
        this.snapshotAllAttemptedCounter = new AtomicLong();
        this.snapshotAllCompletedCounter = new AtomicLong();
        this.lastSnapshotAllResult = new AtomicReference<SnapshotAllResult>();

        //this.lastSnapshotAllTimestamp = new AtomicLong(0);
        //this.lastSnapshotsCompletedCounter = new AtomicInteger();
        //this.lastSnapshotsFailedCounter = new AtomicInteger();

        this.groups = new TreeSet<String>();
        this.summary = new ConcurrentHashMap<String, SummaryGroup<S>>();
        this.snapshots = new ConcurrentHashMap<String, TimeSeries<ObserveAggregateSnapshot<A>>>();

        // always make sure "current" is added to set of vars to track
        // "current" always tracked for all
        this.serviceConfig.getPeriods().add(SummaryPeriod.parse("current"));

        //
        // create high performance http client that can be reused by all observers
        //
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));

        PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
        // increase max total connection to 200
        cm.setMaxTotal(200);
        // increase default max connection per route to 20
        cm.setDefaultMaxPerRoute(20);

        this.httpclient = new DefaultHttpClient(cm);
        this.httpclient.setHttpRequestRetryHandler(new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                // always return false -- we never want a request retried
                return false;
            }
        });
        this.httpclient.getParams()
                .setIntParameter(CoreConnectionPNames.SO_TIMEOUT,
                        new Integer((int) serviceConfig.getSocketTimeout()))
                .setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
                        new Integer((int) serviceConfig.getConnectionTimeout()))
                .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
                .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
                .setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false)
                .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false);
    }

    public ServiceObserverConfiguration getConfiguration() {
        return serviceConfig;
    }

    public String getServiceName() {
        return serviceConfig.getName();
    }

    static public Set<String> createGroupSet(String groups) throws Exception {
        // always create a set regardless if groups is null
        Set<String> groupSet = new TreeSet<String>();

        if (groups != null) {
            String[] tokens = groups.split(",");
            for (String t : tokens) {
                String y = t.trim();
                if (y.equalsIgnoreCase(DEFAULT_GROUP)) {
                    throw new Exception("Reserved group name [" + DEFAULT_GROUP + " prohibited");
                }
                groupSet.add(y);
            }
        }

        // always add "default" group
        groupSet.add(DEFAULT_GROUP);

        return groupSet;
    }

    public void addObserver(String url) throws Exception {
        addObserver(url, null, null);
    }

    public void addObserver(String url, String groups) throws Exception {
        addObserver(url, groups, null);
    }

    public void addObserver(String url, String groups, String name) throws Exception {
        ObserverConfiguration config = new ObserverConfiguration();
        config.setUrl(url);

        String observerName = name;
        if (name == null) {
            observerName = this.ons.createName(url);
        }
        config.setName(observerName);

        Set<String> groupSet = createGroupSet(groups);
        config.setGroups(groupSet);

        Observer<D> observer = new Observer<D>(serviceConfig, config, dataClass, contentParser, httpclient,
                observerName);

        addObserver(observer);
    }

    private void addObserver(Observer<D> observer) throws Exception {
        // verify this observer doesn't already exist
        if (observers.containsKey(observer.getName())) {
            throw new Exception("Observer with name [" + observer.getName()
                    + "] already exists (observer names MUST be unique)");
        }

        // add to list of observers as well as group
        observers.put(observer.getName(), observer);
        this.groups.addAll(observer.getConfiguration().getGroups());
    }

    public int getObserverCount() {
        return this.observers.size();
    }

    public Timer getSnapshotAllTimer() {
        return snapshotAllTimer;
    }

    public SnapshotAllResult getLastResult() {
        return lastSnapshotAllResult.get();
    }

    public long getLastSnapshotAllTimestamp() {
        SnapshotAllResult result = this.lastSnapshotAllResult.get();
        if (result == null) {
            return 0;
        }
        return result.getBeginTimestamp();
    }

    public long getLastSnapshotAllTime() {
        SnapshotAllResult result = this.lastSnapshotAllResult.get();
        if (result == null) {
            return -1;
        }
        return result.getResponseTime();
    }

    public DateTime getLastSnapshotAllDateTime() {
        SnapshotAllResult result = this.lastSnapshotAllResult.get();
        if (result == null) {
            return null;
        }
        return new DateTime(result.getBeginTimestamp(), DateTimeZone.UTC);
    }

    public Exception getLastSnapshotAllException() {
        SnapshotAllResult result = this.lastSnapshotAllResult.get();
        if (result == null) {
            return null;
        }
        return result.getException();
    }

    public int getLastSnapshotsAttempted() {
        SnapshotAllResult result = this.lastSnapshotAllResult.get();
        if (result == null) {
            return 0;
        }
        return result.getSnapshotsAttempted();
    }

    public int getLastSnapshotsCompleted() {
        SnapshotAllResult result = this.lastSnapshotAllResult.get();
        if (result == null) {
            return 0;
        }
        return result.getSnapshotsCompleted();
    }

    public int getLastSnapshotsFailed() {
        SnapshotAllResult result = this.lastSnapshotAllResult.get();
        if (result == null) {
            return 0;
        }
        return result.getSnapshotsFailed();
    }

    public Set<String> getGroups() {
        return groups;
    }

    public Set<SummaryPeriod> getPeriods() {
        return this.serviceConfig.getPeriods();
    }

    public long getRetentionMillis() {
        return this.serviceConfig.getPeriods().last().getMillis();
    }

    public ConcurrentHashMap<String, Observer<D>> getObservers() {
        return observers;
    }

    public ConcurrentHashMap<String, SummaryGroup<S>> getSummary() {
        return summary;
    }

    public ConcurrentHashMap<String, TimeSeries<ObserveAggregateSnapshot<A>>> getSnapshots() {
        return snapshots;
    }

    public SnapshotAllResult snapshotAll() throws Exception {
        final SnapshotAllResult result = new SnapshotAllResult();

        result.beginTimestamp = System.currentTimeMillis();
        final TimerContext timerContext = snapshotAllTimer.time();
        try {
            snapshotAllAttemptedCounter.incrementAndGet();
            doSnapshotAll(result);
            // if we get to here then the snapshotAll call worked
            snapshotAllCompletedCounter.incrementAndGet();
        } catch (Exception e) {
            // save exception into result
            result.exception = e;
            throw e;
        } finally {
            result.completeTimestamp = System.currentTimeMillis();
            timerContext.stop();
            // always save reference to result
            this.lastSnapshotAllResult.set(result);
        }

        return result;
    }

    private void doSnapshotAll(SnapshotAllResult result) throws Exception {
        //
        // create list of snapshots that will be executed
        //
        ArrayList<SnapshotTask> snapshotTasks = new ArrayList<SnapshotTask>();
        for (Observer<D> observer : observers.values()) {
            snapshotTasks.add(new SnapshotTask(observer, result.beginTimestamp));
        }
        result.snapshotsAttempted = snapshotTasks.size();

        // this will run all the update tasks and wait for them all to finish
        executor.invokeAll(snapshotTasks);

        // create an aggregate for each group
        TreeMap<String, ObserveAggregateSnapshot<A>> aggs = new TreeMap<String, ObserveAggregateSnapshot<A>>();

        // process deltas from each observer
        for (Observer<D> observer : observers.values()) {

            // determine if last snapshot completed or failed
            if (observer.getConsecutiveSnapshotCompletedCount() > 0) {
                result.snapshotsCompleted++;
            } else {
                result.snapshotsFailed++;
            }

            // was this the first snapshot attempt for this observer?
            long snapshotAttempts = observer.getSnapshotAttemptCounter();

            // each group will aggregate the same delta snapshot from each observer
            ObserveDeltaSnapshot<D> ods = observer.getDeltaSnapshot();

            if (ods == null) {
                //logger.debug("delta snapshot for observer {} was null", observer.getName());
                SnapshotException e = observer.getException();
                if (e == null) {
                    if (snapshotAttempts <= 1) {
                        // first runs we don't expect any deltas
                    } else {
                        logger.error(
                                "observer [{}] for service [{}] had null delta AND exception values (previous snapshot maybe failed?)",
                                observer.getName(), getServiceName());
                    }
                } else {
                    // this is now logged in SnapshotTask below
                    //logger.warn("exception during snapshot for observer " + observer.getName(), e);
                }
            } else {
                // period should be the same across all deltas
                TimePeriod period = ods.getPeriod();
                // TODO: verify periods match each other as safety check?

                // create or get aggregate for each group this observer belongs to
                for (String group : observer.configuration.getGroups()) {
                    ObserveAggregateSnapshot<A> oas = aggs.get(group);
                    if (oas == null) {
                        oas = new ObserveAggregateSnapshot<A>(period, aggregateClass.newInstance());
                        aggs.put(group, oas);
                    }
                    oas.add(observer.getName(), ods.getData());
                }
            }
        }

        if (snapshotAllAttemptedCounter.get() > 1 && aggs.isEmpty()) {
            logger.warn("snapshotAll() for service [{}] generated no aggregated snapshots!", this.getServiceName());
        }

        // at this point, the new snapshots from each observer have generated
        // new aggregates for this point-in-time -- add this to our rolling time series
        for (String group : aggs.keySet()) {
            // last aggregate snapshot
            ObserveAggregateSnapshot<A> oas = aggs.get(group);

            // get or create new series of aggregate snapshots for each group
            TimeSeries<ObserveAggregateSnapshot<A>> aggseries = snapshots.get(group);

            if (aggseries == null) {
                // figure out capacity of time series (retentionTime / step + fudgeFactor)
                long retentionMillis = getRetentionMillis();
                int initialCapacity = (int) (retentionMillis / this.serviceConfig.getStepMillis()) + 2;
                logger.info(
                        "Creating new TimeSeries for service [{}] group [{}] with retentionMillis="
                                + retentionMillis + "; initialCapacity=" + initialCapacity,
                        getServiceName(), group);
                aggseries = new TimeSeries<ObserveAggregateSnapshot<A>>(retentionMillis, initialCapacity);
                snapshots.put(group, aggseries);
            }

            // add aggregate snapshot to the time series for each group
            // this will also prune old snapshots that are older than the retention period
            // the timestamp of the aggregate becomes the relative "now" timestamp for calculating retentions
            // this is how we'll always at least keep "current" times
            aggseries.add(oas, oas.getTimestamp());

            // create an updated summary for each interval for this group
            SummaryGroupFactory<S, A> sfg = new SummaryGroupFactory<S, A>(oas.getTimestamp(), this.summaryClass,
                    this.serviceConfig.getPeriods());

            sfg.beginAll();

            Iterator<ObserveAggregateSnapshot<A>> it = aggseries.getSeries().iterator();
            while (it.hasNext()) {
                ObserveAggregateSnapshot<A> tempoas = it.next();
                sfg.summarize(tempoas.getPeriod(), tempoas.getAggregate());
            }

            sfg.completeAll();

            SummaryGroup<S> sg = sfg.createSummaryGroup();
            summary.put(group, sg);
        }
    }

    static public class SnapshotAllResult {

        private long beginTimestamp;
        private long completeTimestamp;
        private int snapshotsAttempted;
        private int snapshotsCompleted;
        private int snapshotsFailed;
        private Exception exception;

        public SnapshotAllResult() {
            // do nothing
        }

        public long getBeginTimestamp() {
            return beginTimestamp;
        }

        public long getCompleteTimestamp() {
            return completeTimestamp;
        }

        public long getResponseTime() {
            return completeTimestamp - beginTimestamp;
        }

        public int getSnapshotsAttempted() {
            return snapshotsAttempted;
        }

        public int getSnapshotsCompleted() {
            return snapshotsCompleted;
        }

        public int getSnapshotsFailed() {
            return snapshotsFailed;
        }

        public Exception getException() {
            return exception;
        }

    }

    class SnapshotTask implements Callable<Object> {

        private final Observer observer;
        private final long timestamp;

        public SnapshotTask(Observer observer, long timestamp) {
            this.observer = observer;
            this.timestamp = timestamp;
        }

        @Override
        public Object call() throws Exception {
            long start = System.currentTimeMillis();
            Exception exception = null;
            try {
                observer.snapshot(timestamp);
                return null;
            } catch (Exception e) {
                exception = e;
                return null;
            } finally {
                long stop = System.currentTimeMillis();
                long duration = stop - start;
                StringBuilder sb = new StringBuilder();
                sb.append("snapshot for service [").append(serviceConfig.getName()).append("] observer [")
                        .append(observer.getName()).append("]: time=").append((stop - start)).append(" ms");
                if (exception == null) {
                    sb.append("; result was ok");
                    // only print out good case if > step
                    if (duration >= serviceConfig.getStepMillis()) {
                        logger.warn("{} but exceeded stepMillis", sb);
                    }
                } else {
                    sb.append("; exception: ").append(exception.getMessage());
                    logger.warn("{}", sb);
                    logger.warn("", exception);
                }
            }
        }
    }
}