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.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; } }