de.otto.mongodb.profiler.collection.DefaultCollectionProfiler.java Source code

Java tutorial

Introduction

Here is the source code for de.otto.mongodb.profiler.collection.DefaultCollectionProfiler.java

Source

/*
 *  Copyright 2013 Robert Gacki <robert.gacki@cgi.com>
 *
 *  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.
 */
package de.otto.mongodb.profiler.collection;

import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import de.otto.mongodb.profiler.AbstractAsyncProfiler;
import de.otto.mongodb.profiler.InitializingProfiler;
import de.otto.mongodb.profiler.util.ProfilerThreadFactory;
import org.joda.time.DateTime;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkNotNull;
import static de.otto.mongodb.profiler.util.Async.shutdownAndAwaitTermination;

/**
 * Erstellt Collection-Profile.
 *
 * @author Robert Gacki
 */
public class DefaultCollectionProfiler extends AbstractAsyncProfiler
        implements CollectionProfiler, InitializingProfiler {

    public static final Set<String> IGNORABLE_NS = Collections.unmodifiableSet(new HashSet<String>() {
        {
            add("system.namespaces");
            add("system.indexes");
            add("system.profile");
            add("system.js");
            add("system.users");
        }
    });

    public static final long DEFAULT_DELAY = 60L;
    public static final long DEFAULT_INITIAL_DELAY = 60L;
    public static final TimeUnit DEFAULT_TIMEUNIT = TimeUnit.SECONDS;

    public static final long DEFAULT_CLEANUP_AGE = 1;
    public static final TimeUnit DEFAULT_CLEANUP_AGE_TIMEUNIT = TimeUnit.DAYS;
    public static final long DEFAULT_CLEANUP_DELAY = 15;
    public static final TimeUnit DEFAULT_CLEANUP_DELAY_TIMEUNIT = TimeUnit.MINUTES;

    private final ScheduledExecutorService analyzerExecutorService;
    private final ScheduledExecutorService managementExecutorService;

    private final Map<String, DefaultCollectionProfile> collectionProfiles;

    private final DB database;

    private final Object collectionStockMonitor = new Object();

    private volatile boolean shutdown = false;

    public DefaultCollectionProfiler(final boolean execute, final DB database, final ThreadGroup threadGroup) {
        this(execute, database,
                Executors.newSingleThreadScheduledExecutor(
                        new ProfilerThreadFactory(CollectionProfiler.class, "Analyzer", threadGroup)),
                Executors.newScheduledThreadPool(1,
                        new ProfilerThreadFactory(CollectionProfiler.class, "Manager", threadGroup)));
    }

    protected DefaultCollectionProfiler(final boolean execute, final DB database,
            final ScheduledExecutorService analyzerExecutorService,
            final ScheduledExecutorService managementExecutorService) {
        super(execute);
        this.database = database;
        this.analyzerExecutorService = analyzerExecutorService;
        this.managementExecutorService = managementExecutorService;
        this.collectionProfiles = Collections.synchronizedMap(new HashMap<String, DefaultCollectionProfile>());
    }

    @Override
    public DefaultCollectionProfile addProfile(String collectionName) throws CollectionDoesNotExistException {
        synchronized (collectionStockMonitor) {

            final DBCollection collection = findCollection(collectionName);

            if (collection == null) {
                throw new CollectionDoesNotExistException(collectionName);
            }

            return getOrCreateProfile(collectionName, collection.isCapped());
        }
    }

    @Override
    public void removeProfile(String collectionName) {
        synchronized (collectionStockMonitor) {
            collectionProfiles.remove(collectionName);
        }
    }

    @Override
    public Set<String> getAvailableCollections() {

        final Set<String> collectionNames = new HashSet<>(database.getCollectionNames());
        collectionNames.removeAll(IGNORABLE_NS);
        collectionNames.removeAll(collectionProfiles.keySet());
        return collectionNames;
    }

    @Override
    public void reset() {
        for (DefaultCollectionProfile profile : collectionProfiles.values()) {
            profile.reset();
        }
    }

    @Override
    public synchronized boolean continueProfiling() {
        if (shutdown) {
            return false;
        }
        return super.continueProfiling();
    }

    @Override
    public synchronized DateTime continueProfiling(long duration, TimeUnit timeUnit) {
        if (shutdown) {
            return null;
        }
        return super.continueProfiling(duration, timeUnit);
    }

    @Override
    public synchronized void initialize() {

        if (shutdown) {
            throw new IllegalStateException("Profiler is shut down!");
        }

        this.analyzerExecutorService.scheduleWithFixedDelay(new CollectDataJob(this, getExecutionGuard()),
                DEFAULT_INITIAL_DELAY, DEFAULT_DELAY, DEFAULT_TIMEUNIT);
        final CleanupDataJob cleanupDataJob = new CleanupDataJob(collectionProfiles.values(), DEFAULT_CLEANUP_AGE,
                DEFAULT_CLEANUP_AGE_TIMEUNIT);
        this.managementExecutorService.scheduleAtFixedRate(cleanupDataJob, DEFAULT_CLEANUP_DELAY,
                DEFAULT_CLEANUP_DELAY, DEFAULT_CLEANUP_DELAY_TIMEUNIT);
    }

    @Override
    public synchronized void shutdown() {

        if (shutdown) {
            return;
        }

        shutdown = true;

        stopProfiling();

        try {
            shutdownAndAwaitTermination(analyzerExecutorService, 60, TimeUnit.SECONDS);
        } finally {
            shutdownAndAwaitTermination(managementExecutorService, 60, TimeUnit.SECONDS);
        }
    }

    @Override
    public List<DefaultCollectionProfile> getProfiles() {
        return new ArrayList<>(collectionProfiles.values());
    }

    @Override
    public DefaultCollectionProfile getProfile(String collectionName) {
        return collectionProfiles.get(collectionName);
    }

    private DBCollection findCollection(String name) {

        checkNotNull(name);

        if (!database.collectionExists(name)) {
            return null;
        }

        return database.getCollection(name);
    }

    @Override
    public void newSample() {

        // reduces lock contention on synchronized list
        final List<DefaultCollectionProfile> collectionProfiles = new ArrayList<>(this.collectionProfiles.values());
        for (DefaultCollectionProfile collectionProfile : collectionProfiles) {
            newSample(collectionProfile);
        }
    }

    protected void newSample(DefaultCollectionProfile profile) {

        final DBCollection collection = database.getCollection(profile.getCollectionName());
        final CommandResult result = collection.getStats();
        if (result.ok()) {
            profile.add(result);
        } else {
            if ("ns not found".equals(result.getErrorMessage())) {
                profile.reset();
            }
        }
    }

    public Map<Long, Long> getListSizeDistribution(String ns, String attribute) {

        final DBCollection collection = findCollection(ns);
        if (collection == null) {
            throw new IllegalArgumentException(String.format("No repository found for name [%s]!", ns));
        }
        return ListSizeDistribution.calculate(collection, attribute, null);
    }

    private synchronized DefaultCollectionProfile getOrCreateProfile(String ns, boolean capped) {
        DefaultCollectionProfile profile = this.collectionProfiles.get(ns);
        if (profile == null) {
            profile = new DefaultCollectionProfile(ns, capped);
            this.collectionProfiles.put(ns, profile);
        }
        return profile;
    }

    protected static class CollectDataJob implements Runnable {

        private final DefaultCollectionProfiler profiler;
        private final ExecutionGuard executionGuard;

        private CollectDataJob(DefaultCollectionProfiler profiler, ExecutionGuard executionGuard) {
            this.profiler = profiler;
            this.executionGuard = executionGuard;
        }

        @Override
        public void run() {
            if (executionGuard.execute()) {
                profiler.newSample();
            }
        }
    }

    protected static class CleanupDataJob implements Runnable {

        private final long duration;
        private final TimeUnit timeUnit;
        private final Collection<DefaultCollectionProfile> profiles;

        public CleanupDataJob(Collection<DefaultCollectionProfile> profiles, long duration, TimeUnit timeUnit) {
            this.profiles = profiles;
            this.duration = duration;
            this.timeUnit = timeUnit;
        }

        private DateTime before(long duration, TimeUnit timeUnit) {
            DateTime now = DateTime.now();
            return now.minus(timeUnit.toMillis(duration));
        }

        @Override
        public void run() {
            final DateTime before = before(duration, timeUnit);
            final List<DefaultCollectionProfile> profiles = new ArrayList<>(this.profiles); // reduces lock contention on synchronized list
            for (DefaultCollectionProfile profile : profiles) {
                profile.removeMarks(before);
            }
        }
    }
}