Java tutorial
/* * 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); } } } }