/* * Copyright 2014-2017 the original author or authors. * * 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 org.glowroot.ui; import java.util.List; import javax.annotation.Nullable; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import io.netty.handler.codec.http.HttpResponseStatus; import org.immutables.value.Value; import org.glowroot.common.repo.ConfigRepository; import org.glowroot.common.repo.GaugeValueRepository; import org.glowroot.common.repo.GaugeValueRepository.Gauge; import org.glowroot.common.repo.Utils; import org.glowroot.common.repo.util.Gauges; import org.glowroot.common.util.Formatting; import org.glowroot.common.util.ObjectMappers; import org.glowroot.common.util.Styles; import org.glowroot.common.util.Versions; import org.glowroot.ui.GaugeValueJsonService.GaugeOrdering; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AlertConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AlertConfig.AlertKind; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.SyntheticMonitorConfig; import org.glowroot.wire.api.model.Proto.OptionalDouble; import org.glowroot.wire.api.model.Proto.OptionalInt32; @JsonService class AlertConfigJsonService { private static final ObjectMapper mapper = ObjectMappers.create(); @VisibleForTesting static final Ordering<AlertListItem> orderingByName = new Ordering<AlertListItem>() { @Override public int compare(AlertListItem left, AlertListItem right) { return left.display().compareToIgnoreCase(right.display()); } }; private final ConfigRepository configRepository; private final GaugeValueRepository gaugeValueRepository; private final boolean central; AlertConfigJsonService(ConfigRepository configRepository, GaugeValueRepository gaugeValueRepository, boolean central) { this.configRepository = configRepository; this.gaugeValueRepository = gaugeValueRepository; this.central = central; } // central supports alert configs on rollups @GET(path = "/backend/config/alerts", permission = "agent:config:view:alert") String getAlert(@BindAgentRollupId String agentRollupId, @BindRequest AlertConfigRequest request) throws Exception { Optional<String> version = request.version(); if (version.isPresent()) { AlertConfig alertConfig = configRepository.getAlertConfig(agentRollupId, version.get()); if (alertConfig == null) { throw new JsonServiceException(HttpResponseStatus.NOT_FOUND); } return getAlertResponse(agentRollupId, alertConfig); } else { List<AlertListItem> alertListItems = Lists.newArrayList(); List<AlertConfig> alertConfigs = configRepository.getAlertConfigs(agentRollupId); for (AlertConfig alertConfig : alertConfigs) { alertListItems.add(ImmutableAlertListItem.of(Versions.getVersion(alertConfig), getAlertDisplay(agentRollupId, alertConfig, configRepository))); } alertListItems = orderingByName.immutableSortedCopy(alertListItems); return mapper.writeValueAsString(alertListItems); } } // central supports alert configs on rollups @GET(path = "/backend/config/alert-dropdowns", permission = "agent:config:view:alert") String getAlertDropdowns(@BindAgentRollupId String agentRollupId) throws Exception { return mapper.writeValueAsString(ImmutableAlertConfigResponse.builder() .addAllGauges(getGaugeDropdownItems(agentRollupId)) .addAllSyntheticMonitors(getSyntheticMonitorDropdownItems(agentRollupId)) .build()); } // central supports alert configs on rollups @POST(path = "/backend/config/alerts/add", permission = "agent:config:edit:alert") String addAlert(@BindAgentRollupId String agentRollupId, @BindRequest AlertConfigDto configDto) throws Exception { AlertConfig alertConfig = configDto.convert(); configRepository.insertAlertConfig(agentRollupId, alertConfig); return getAlertResponse(agentRollupId, alertConfig); } // central supports alert configs on rollups @POST(path = "/backend/config/alerts/update", permission = "agent:config:edit:alert") String updateAlert(@BindAgentRollupId String agentRollupId, @BindRequest AlertConfigDto configDto) throws Exception { AlertConfig alertConfig = configDto.convert(); configRepository.updateAlertConfig(agentRollupId, alertConfig, configDto.version().get()); return getAlertResponse(agentRollupId, alertConfig); } // central supports alert configs on rollups @POST(path = "/backend/config/alerts/remove", permission = "agent:config:edit:alert") void removeAlert(@BindAgentRollupId String agentRollupId, @BindRequest AlertConfigRequest request) throws Exception { configRepository.deleteAlertConfig(agentRollupId, request.version().get()); } private String getAlertResponse(String agentRollupId, AlertConfig alertConfig) throws Exception { return mapper.writeValueAsString(ImmutableAlertConfigResponse.builder() .config(AlertConfigDto.create(alertConfig)) .heading(getAlertDisplay(agentRollupId, alertConfig, configRepository)) .addAllGauges(getGaugeDropdownItems(agentRollupId)) .addAllSyntheticMonitors(getSyntheticMonitorDropdownItems(agentRollupId)) .build()); } private List<Gauge> getGaugeDropdownItems(String agentRollupId) throws Exception { List<Gauge> gauges = gaugeValueRepository.getGauges(agentRollupId); return new GaugeOrdering().immutableSortedCopy(gauges); } private List<SyntheticMonitorItem> getSyntheticMonitorDropdownItems(String agentRollupId) throws Exception { if (!central) { return ImmutableList.of(); } List<SyntheticMonitorItem> items = Lists.newArrayList(); for (SyntheticMonitorConfig config : configRepository .getSyntheticMonitorConfigs(agentRollupId)) { items.add(ImmutableSyntheticMonitorItem.of(config.getId(), config.getDisplay())); } return items; } static String getAlertDisplay(String agentRollupId, AlertConfig alertCondition, ConfigRepository configRepository) throws Exception { switch (alertCondition.getKind()) { case TRANSACTION: return getTransactionAlertDisplay(alertCondition); case GAUGE: return getGaugeAlertDisplay(alertCondition); case HEARTBEAT: return getHeartbeatAlertDisplay(alertCondition); case SYNTHETIC_MONITOR: SyntheticMonitorConfig syntheticMonitorConfig = configRepository.getSyntheticMonitorConfig(agentRollupId, alertCondition.getSyntheticMonitorId()); return getSyntheticMonitorAlertDisplay(alertCondition, syntheticMonitorConfig); default: throw new IllegalStateException( "Unexpected alert kind: " + alertCondition.getKind()); } } private static String getTransactionAlertDisplay(AlertConfig alertCondition) { StringBuilder sb = new StringBuilder(); sb.append(alertCondition.getTransactionType()); sb.append(" - "); sb.append(Utils .getPercentileWithSuffix(alertCondition.getTransactionPercentile().getValue())); sb.append(" percentile over the last "); sb.append(alertCondition.getTimePeriodSeconds() / 60); sb.append(" minute"); if (alertCondition.getTimePeriodSeconds() != 60) { sb.append("s"); } sb.append(" exceeds "); int thresholdMillis = alertCondition.getThresholdMillis().getValue(); sb.append(thresholdMillis); sb.append(" millisecond"); if (thresholdMillis != 1) { sb.append("s"); } return sb.toString(); } private static String getGaugeAlertDisplay(AlertConfig alertCondition) { Gauge gauge = Gauges.getGauge(alertCondition.getGaugeName()); StringBuilder sb = new StringBuilder(); sb.append("Gauge - "); sb.append(gauge.display()); sb.append(" - average over the last "); sb.append(alertCondition.getTimePeriodSeconds() / 60); sb.append(" minute"); if (alertCondition.getTimePeriodSeconds() != 60) { sb.append("s"); } sb.append(" exceeds "); double value = alertCondition.getGaugeThreshold().getValue(); String unit = gauge.unit(); if (unit.equals("bytes")) { sb.append(Formatting.formatBytes((long) value)); } else if (!unit.isEmpty()) { sb.append(Formatting.displaySixDigitsOfPrecision(value)); sb.append(" "); sb.append(unit); } else { sb.append(Formatting.displaySixDigitsOfPrecision(value)); } return sb.toString(); } private static String getHeartbeatAlertDisplay(AlertConfig alertCondition) { StringBuilder sb = new StringBuilder(); sb.append("Heartbeat - not received over the last "); int timePeriodSeconds = alertCondition.getTimePeriodSeconds(); sb.append(timePeriodSeconds); sb.append(" second"); if (timePeriodSeconds != 1) { sb.append("s"); } return sb.toString(); } private static String getSyntheticMonitorAlertDisplay(AlertConfig alertCondition, @Nullable SyntheticMonitorConfig syntheticMonitorConfig) { StringBuilder sb = new StringBuilder(); sb.append("Synthetic monitor - "); if (syntheticMonitorConfig == null) { sb.append("<NOT FOUND>"); } else { sb.append(syntheticMonitorConfig.getDisplay()); } sb.append(" exceeds "); int thresholdMillis = alertCondition.getThresholdMillis().getValue(); sb.append(thresholdMillis); sb.append(" millisecond"); if (thresholdMillis != 1) { sb.append("s"); } return sb.toString(); } @Value.Immutable interface AlertConfigRequest { Optional<String> version(); } @Value.Immutable @Styles.AllParameters interface AlertListItem { String version(); String display(); } @Value.Immutable interface AlertConfigResponse { @Nullable AlertConfigDto config(); @Nullable String heading(); List<Gauge> gauges(); List<SyntheticMonitorItem> syntheticMonitors(); } @Value.Immutable @Styles.AllParameters interface SyntheticMonitorItem { String id(); String display(); } @Value.Immutable abstract static class AlertConfigDto { abstract AlertKind kind(); abstract @Nullable String transactionType(); abstract @Nullable Double transactionPercentile(); abstract @Nullable Integer minTransactionCount(); abstract @Nullable String gaugeName(); abstract @Nullable Double gaugeThreshold(); abstract @Nullable String gaugeDisplay(); // only used in response abstract List<String> gaugeDisplayPath(); // only used in response abstract @Nullable String gaugeUnit(); // only used in response abstract @Nullable String gaugeGrouping(); // only used in response abstract @Nullable String syntheticMonitorId(); abstract @Nullable Integer thresholdMillis(); abstract @Nullable Integer timePeriodSeconds(); abstract ImmutableList<String> emailAddresses(); abstract Optional<String> version(); // absent for insert operations private AlertConfig convert() { AlertConfig.Builder builder = AlertConfig.newBuilder() .setKind(kind()); String transactionType = transactionType(); if (transactionType != null) { builder.setTransactionType(transactionType); } Double transactionPercentile = transactionPercentile(); if (transactionPercentile != null) { builder.setTransactionPercentile(OptionalDouble.newBuilder() .setValue(transactionPercentile)); } Integer minTransactionCount = minTransactionCount(); if (minTransactionCount != null) { builder.setMinTransactionCount(OptionalInt32.newBuilder() .setValue(minTransactionCount)); } String gaugeName = gaugeName(); if (gaugeName != null) { builder.setGaugeName(gaugeName); } Double gaugeThreshold = gaugeThreshold(); if (gaugeThreshold != null) { builder.setGaugeThreshold(OptionalDouble.newBuilder() .setValue(gaugeThreshold)); } String syntheticMonitorId = syntheticMonitorId(); if (syntheticMonitorId != null) { builder.setSyntheticMonitorId(syntheticMonitorId); } Integer thresholdMillis = thresholdMillis(); if (thresholdMillis != null) { builder.setThresholdMillis(OptionalInt32.newBuilder().setValue(thresholdMillis)); } Integer timePeriodSeconds = timePeriodSeconds(); if (timePeriodSeconds != null) { builder.setTimePeriodSeconds(timePeriodSeconds); } builder.addAllEmailAddress(emailAddresses()); return builder.build(); } private static AlertConfigDto create(AlertConfig alertConfig) { Gauge gauge = null; if (alertConfig.getKind() == AlertKind.GAUGE) { gauge = Gauges.getGauge(alertConfig.getGaugeName()); } ImmutableAlertConfigDto.Builder builder = ImmutableAlertConfigDto.builder() .kind(alertConfig.getKind()); String transactionType = alertConfig.getTransactionType(); if (!transactionType.isEmpty()) { builder.transactionType(transactionType); } if (alertConfig.hasTransactionPercentile()) { builder.transactionPercentile(alertConfig.getTransactionPercentile().getValue()); } if (alertConfig.hasMinTransactionCount()) { builder.minTransactionCount(alertConfig.getMinTransactionCount().getValue()); } String gaugeName = alertConfig.getGaugeName(); if (!gaugeName.isEmpty()) { builder.gaugeName(gaugeName); } if (alertConfig.hasGaugeThreshold()) { builder.gaugeThreshold(alertConfig.getGaugeThreshold().getValue()); } if (gauge != null) { builder.gaugeDisplay(gauge.display()) .gaugeDisplayPath(gauge.displayPath()); } String syntheticMonitorId = alertConfig.getSyntheticMonitorId(); if (!syntheticMonitorId.isEmpty()) { builder.syntheticMonitorId(syntheticMonitorId); } if (alertConfig.hasThresholdMillis()) { builder.thresholdMillis(alertConfig.getThresholdMillis().getValue()); } int timePeriodSeconds = alertConfig.getTimePeriodSeconds(); if (timePeriodSeconds != 0) { builder.timePeriodSeconds(timePeriodSeconds); } return builder.gaugeUnit(gauge == null ? "" : gauge.unit()) .gaugeGrouping(gauge == null ? "" : gauge.grouping()) .addAllEmailAddresses(alertConfig.getEmailAddressList()) .version(Versions.getVersion(alertConfig)) .build(); } } }