de.otto.mongodb.profiler.web.CollectionProfileController.java Source code

Java tutorial

Introduction

Here is the source code for de.otto.mongodb.profiler.web.CollectionProfileController.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.web;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import de.otto.mongodb.profiler.ChronoSampler;
import de.otto.mongodb.profiler.ProfiledDatabase;
import de.otto.mongodb.profiler.ProfilerService;
import de.otto.mongodb.profiler.collection.CollectionDoesNotExistException;
import de.otto.mongodb.profiler.collection.CollectionProfile;
import de.otto.mongodb.profiler.collection.CollectionProfiler;
import de.otto.mongodb.profiler.util.Logger;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponentsBuilder;

import javax.validation.Valid;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping(CollectionProfileController.CONTROLLER_RESOURCE)
public class CollectionProfileController extends AbstractController {

    private static final Logger logger = Logger.getLogger(CollectionProfileController.class);

    public static final String CONTROLLER_RESOURCE = "/connections/{connectionId:.+}/databases/{databaseName:.+}/collections";

    public CollectionProfileController(ProfilerService profilerService) {
        super(profilerService);
    }

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
    public View showCollectionProfiles(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            final UriComponentsBuilder uriComponentsBuilder) throws ResourceNotFoundException {

        final String uri = uriComponentsBuilder.path("/connections/{connectionId}/databases/{databaseName}")
                .fragment("Collections").buildAndExpand(connectionId, databaseName).toUriString();

        return new RedirectView(uri);
    }

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE, params = "render=panel")
    public ModelAndView showCollectionProfilesPanel(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            @RequestParam(value = "renderType", required = false) final String renderTypeValue, Locale locale)
            throws ResourceNotFoundException {

        final CollectionProfilesPanelViewModel.RenderType renderType;

        if (renderTypeValue == null || renderTypeValue.isEmpty()) {
            renderType = CollectionProfilesPanelViewModel.RenderType.PANEL;
        } else {
            renderType = CollectionProfilesPanelViewModel.RenderType
                    .valueOf(renderTypeValue.toUpperCase(Locale.ENGLISH));
        }

        final ProfiledDatabase database = requireDatabase(connectionId, databaseName);

        final CollectionProfiler collectionProfiler = getProfilerService().getCollectionProfiler(database);

        final CollectionProfilesPanelViewModel viewModel = new CollectionProfilesPanelViewModel(connectionId,
                databaseName, collectionProfiler, renderType, locale);

        final AddCollectionProfileFormModel model = new AddCollectionProfileFormModel();

        return new ModelAndView("fragment.collection-profiles-panel").addObject("model", viewModel)
                .addObject("collection", model);
    }

    @Page(mainNavigation = MainNavigation.DATABASES)
    @RequestMapping(value = "/{collectionName:.+}", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView showCollectionProfile(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            @PathVariable("collectionName") final String collectionName, Locale locale)
            throws ResourceNotFoundException {

        final ProfiledDatabase database = requireDatabase(connectionId, databaseName);
        final CollectionProfile profile = getProfilerService().getCollectionProfiler(database)
                .getProfile(collectionName);

        if (profile == null) {
            throw new ResourceNotFoundException(
                    String.format("No profile found for collection [%s]!", collectionName));
        }

        final CollectionProfilePageViewModel viewModel = new CollectionProfilePageViewModel(profile, database,
                locale);

        return new ModelAndView("page.collection-profile").addObject("model", viewModel);
    }

    @Page(mainNavigation = MainNavigation.DATABASES)
    @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public ModelAndView addCollectionProfile(
            @Valid @ModelAttribute("collection") AddCollectionProfileFormModel model,
            final BindingResult bindingResult, @PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            final UriComponentsBuilder uriComponentsBuilder) throws ResourceNotFoundException {

        final ProfiledDatabase database = requireDatabase(connectionId, databaseName);

        if (bindingResult.hasErrors()) {
            return showDatabasePageWith(model, database);
        }

        final CollectionProfiler collectionProfiler = getProfilerService().getCollectionProfiler(database);
        final String uri;

        if (Boolean.TRUE.equals(model.getAddAll())) {
            final Set<String> collections = collectionProfiler.getAvailableCollections();
            for (String collection : collections) {
                try {
                    collectionProfiler.addProfile(collection);
                } catch (CollectionDoesNotExistException e) {
                    logger.debug(e, "Failed to add collection [%s]", collection);
                }
            }
            uri = uriComponentsBuilder.path("/connections/{connectionId}/databases/{databaseName}/collections")
                    .buildAndExpand(connectionId, databaseName).toUriString();
        } else {
            final CollectionProfile collectionProfile;
            try {
                collectionProfile = collectionProfiler.addProfile(model.getName());
            } catch (CollectionDoesNotExistException e) {
                logger.debug(e, "Failed to add collection [%s]", model.getName());
                bindingResult.rejectValue("name", "collectionDoesNotExist");
                return showDatabasePageWith(model, database);
            }

            uri = uriComponentsBuilder
                    .path("/connections/{connectionId}/databases/{databaseName}/collections/{collectionName}")
                    .buildAndExpand(connectionId, databaseName, collectionProfile.getCollectionName())
                    .toUriString();
        }

        return new ModelAndView(new RedirectView(uri));
    }

    private ModelAndView showDatabasePageWith(AddCollectionProfileFormModel model, ProfiledDatabase database) {

        final DatabasePageViewModel viewModel = new DatabasePageViewModel(database);

        return new ModelAndView("page.database").addObject("model", viewModel).addObject("collection", model);
    }

    @RequestMapping(value = "/control", method = RequestMethod.POST, params = "action=startProfiling", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public View startProfiling(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            final UriComponentsBuilder uriComponentsBuilder) throws Exception {

        requireProfiler(connectionId, databaseName).continueProfiling();

        return new RedirectView(uriComponentsBuilder.path("/connections/{connectionId}/databases/{databaseName}")
                .fragment("Collections").buildAndExpand(connectionId, databaseName).toUriString());
    }

    @RequestMapping(value = "/control", method = RequestMethod.POST, params = "action=stopProfiling", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public View stopProfiling(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            final UriComponentsBuilder uriComponentsBuilder) throws Exception {

        requireProfiler(connectionId, databaseName).stopProfiling();

        return new RedirectView(uriComponentsBuilder.path("/connections/{connectionId}/databases/{databaseName}")
                .fragment("Collections").buildAndExpand(connectionId, databaseName).toUriString());
    }

    @RequestMapping(value = "/control", method = RequestMethod.POST, params = "action=reset", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public View resetProfiles(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            final UriComponentsBuilder uriComponentsBuilder) throws Exception {

        requireProfiler(connectionId, databaseName).reset();

        return new RedirectView(uriComponentsBuilder.path("/connections/{connectionId}/databases/{databaseName}")
                .fragment("Collections").buildAndExpand(connectionId, databaseName).toUriString());
    }

    @RequestMapping(value = "/control", method = RequestMethod.POST, params = "action=newSample", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public View takeSample(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            final UriComponentsBuilder uriComponentsBuilder) throws Exception {

        requireProfiler(connectionId, databaseName).newSample();

        return new RedirectView(uriComponentsBuilder.path("/connections/{connectionId}/databases/{databaseName}")
                .fragment("Collections").buildAndExpand(connectionId, databaseName).toUriString());
    }

    private static final class DocumentCountSample {

        public final long time;
        public final long documentCount;

        private DocumentCountSample(long time, long documentCount) {
            this.time = time;
            this.documentCount = documentCount;
        }
    }

    private static final ChronoSampler.Reduction<CollectionProfile.Mark, DocumentCountSample> DOCUMENT_COUNT_REDUCTION = new ChronoSampler.Reduction<CollectionProfile.Mark, DocumentCountSample>() {

        @Override
        public DocumentCountSample reduce(long time, Deque<CollectionProfile.Mark> marks) {
            if (marks.isEmpty()) {
                return null;
            }
            final CollectionProfile.Mark last = marks.getLast();
            return new DocumentCountSample(time, last.getDocumentCount());
        }
    };

    @RequestMapping(value = "/{collectionName:.+}/chart-data/document-count", method = RequestMethod.GET, produces = {
            JSON_TYPE_1, JSON_TYPE_2 })
    public HttpEntity<String> getDocumentCountChartData(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            @PathVariable("collectionName") final String collectionName,
            @RequestParam(value = "sampleRate", required = false) Long sampleRate)
            throws ResourceNotFoundException {

        final CollectionProfile profile = requireProfile(connectionId, databaseName, collectionName);

        if (sampleRate == null) {
            sampleRate = 15L;
        }

        final ChronoSampler<CollectionProfile.Mark, DocumentCountSample> sampler = new ChronoSampler<>(sampleRate,
                TimeUnit.MINUTES, DOCUMENT_COUNT_REDUCTION, lowerBoundary(-24));

        for (CollectionProfile.Mark mark : profile.getMarks()) {
            sampler.add(mark.getTime(), mark);
        }

        final List<DocumentCountSample> samples = sampler.finish();

        final JsonArray documentCountValues = new JsonArray();
        for (DocumentCountSample sample : samples) {
            final JsonArray value = new JsonArray();
            value.add(new JsonPrimitive(Long.valueOf(sample.time)));
            value.add(new JsonPrimitive(Long.valueOf(sample.documentCount)));
            documentCountValues.add(value);
        }

        final JsonObject documentCountJson = new JsonObject();
        documentCountJson.add("key", new JsonPrimitive("Document count"));
        documentCountJson.add("values", documentCountValues);

        final JsonObject json = new JsonObject();
        json.add("documentCount", documentCountJson);

        return new HttpEntity<>(json.toString());
    }

    private static final class PaddingFactorSample {

        public final long time;
        public final double paddingFactor;

        private PaddingFactorSample(long time, double paddingFactor) {
            this.time = time;
            this.paddingFactor = paddingFactor;
        }
    }

    private static final ChronoSampler.Reduction<CollectionProfile.Mark, PaddingFactorSample> PADDING_FACTOR_REDUCTION = new ChronoSampler.Reduction<CollectionProfile.Mark, PaddingFactorSample>() {

        @Override
        public PaddingFactorSample reduce(long time, Deque<CollectionProfile.Mark> marks) {
            if (marks.isEmpty()) {
                return null;
            }
            final CollectionProfile.Mark last = marks.getLast();
            return new PaddingFactorSample(time, last.getPaddingFactor());
        }
    };

    @RequestMapping(value = "/{collectionName:.+}/chart-data/padding-factor", method = RequestMethod.GET, produces = {
            JSON_TYPE_1, JSON_TYPE_2 })
    public HttpEntity<String> getPaddingFactorChartData(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            @PathVariable("collectionName") final String collectionName,
            @RequestParam(value = "sampleRate", required = false) Long sampleRate)
            throws ResourceNotFoundException {

        final CollectionProfile profile = requireProfile(connectionId, databaseName, collectionName);

        if (sampleRate == null) {
            sampleRate = 15L;
        }

        final ChronoSampler<CollectionProfile.Mark, PaddingFactorSample> sampler = new ChronoSampler<>(sampleRate,
                TimeUnit.MINUTES, PADDING_FACTOR_REDUCTION, lowerBoundary(-24));

        for (CollectionProfile.Mark mark : profile.getMarks()) {
            sampler.add(mark.getTime(), mark);
        }

        final List<PaddingFactorSample> samples = sampler.finish();

        final JsonArray paddingFactorValues = new JsonArray();
        for (PaddingFactorSample sample : samples) {
            final JsonArray value = new JsonArray();
            value.add(new JsonPrimitive(Long.valueOf(sample.time)));
            value.add(new JsonPrimitive(Double.valueOf(sample.paddingFactor)));
            paddingFactorValues.add(value);
        }

        final JsonObject paddingFactorJson = new JsonObject();
        paddingFactorJson.add("key", new JsonPrimitive("Padding factor"));
        paddingFactorJson.add("values", paddingFactorValues);

        final JsonObject json = new JsonObject();
        json.add("paddingFactor", paddingFactorJson);

        return new HttpEntity<>(json.toString());
    }

    private static final class DataSizeSample {

        public final long time;
        public final long dataSize;
        public final long dataStorageSize;

        private DataSizeSample(final long time, final long dataSize, final long dataStorageSize) {
            this.time = time;
            this.dataSize = dataSize;
            this.dataStorageSize = dataStorageSize;
        }
    }

    private static final ChronoSampler.Reduction<CollectionProfile.Mark, DataSizeSample> DATA_SIZE_REDUCTION = new ChronoSampler.Reduction<CollectionProfile.Mark, DataSizeSample>() {

        @Override
        public DataSizeSample reduce(long time, Deque<CollectionProfile.Mark> marks) {
            if (marks.isEmpty()) {
                return null;
            }
            final CollectionProfile.Mark last = marks.getLast();
            return new DataSizeSample(time, last.getDataSize(), last.getDataStorageSize());
        }
    };

    @RequestMapping(value = "/{collectionName:.+}/chart-data/data-size", method = RequestMethod.GET, produces = {
            JSON_TYPE_1, JSON_TYPE_2 })
    public HttpEntity<String> getDataSizeChartData(@PathVariable("connectionId") final String connectionId,
            @PathVariable("databaseName") final String databaseName,
            @PathVariable("collectionName") final String collectionName,
            @RequestParam(value = "sampleRate", required = false) Long sampleRate)
            throws ResourceNotFoundException {

        final CollectionProfile profile = requireProfile(connectionId, databaseName, collectionName);

        if (sampleRate == null) {
            sampleRate = 15L;
        }

        final ChronoSampler<CollectionProfile.Mark, DataSizeSample> sampler = new ChronoSampler<>(sampleRate,
                TimeUnit.MINUTES, DATA_SIZE_REDUCTION, lowerBoundary(-24));

        for (CollectionProfile.Mark mark : profile.getMarks()) {
            sampler.add(mark.getTime(), mark);
        }

        final List<DataSizeSample> samples = sampler.finish();

        final JsonArray dataSizeValues = new JsonArray();
        final JsonArray dataStorageSizeValues = new JsonArray();
        for (DataSizeSample sample : samples) {
            final JsonArray dataSizeValue = new JsonArray();
            dataSizeValue.add(new JsonPrimitive(Long.valueOf(sample.time)));
            dataSizeValue.add(new JsonPrimitive(Long.valueOf(sample.dataSize)));
            dataSizeValues.add(dataSizeValue);
            final JsonArray dataStorageSizeValue = new JsonArray();
            dataStorageSizeValue.add(new JsonPrimitive(Long.valueOf(sample.time)));
            dataStorageSizeValue.add(new JsonPrimitive(Long.valueOf(sample.dataStorageSize)));
            dataStorageSizeValues.add(dataStorageSizeValue);
        }

        final JsonObject dataSizeJson = new JsonObject();
        dataSizeJson.add("key", new JsonPrimitive("Data size"));
        dataSizeJson.add("values", dataSizeValues);

        final JsonObject dataStorageSizeJson = new JsonObject();
        dataStorageSizeJson.add("key", new JsonPrimitive("Data storage size"));
        dataStorageSizeJson.add("values", dataStorageSizeValues);

        final JsonObject json = new JsonObject();
        json.add("dataSize", dataSizeJson);
        json.add("dataStorageSize", dataStorageSizeJson);

        return new HttpEntity<>(json.toString());
    }

    protected CollectionProfiler requireProfiler(final String connectionId, final String databaseName)
            throws ResourceNotFoundException {

        final CollectionProfiler profiler = getProfilerService()
                .getCollectionProfiler(requireDatabase(connectionId, databaseName));

        if (profiler == null) {
            throw new IllegalStateException(
                    String.format("The CollectionProfiler is required to exist for database [%s] connected via []!",
                            databaseName, connectionId));
        }

        return profiler;
    }

    protected CollectionProfile requireProfile(final String connectionId, final String databaseName,
            final String collectionName) throws ResourceNotFoundException {

        final CollectionProfile profile = requireProfiler(connectionId, databaseName).getProfile(collectionName);

        if (profile == null) {
            throw new ResourceNotFoundException(
                    String.format("No profile found for collection [%s] of database [%s] connected via [%s]!",
                            collectionName, databaseName, connectionId));
        }

        return profile;
    }

}