/* * Copyright 2011-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.agent.config; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import org.immutables.value.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.config.PropertyValue.PropertyType; import org.glowroot.agent.plugin.api.config.ConfigListener; import org.glowroot.agent.util.JavaVersion; import org.glowroot.common.util.ObjectMappers; import org.glowroot.common.util.OnlyUsedByTests; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig; public class ConfigService { private static final Logger logger = LoggerFactory.getLogger(ConfigService.class); private static final ObjectMapper mapper = ObjectMappers.create(); private static final long GAUGE_COLLECTION_INTERVAL_MILLIS = Long.getLong("glowroot.internal.gaugeCollectionIntervalMillis", 5000); private final ConfigFile configFile; private final ImmutableList<PluginDescriptor> pluginDescriptors; private final Set<ConfigListener> configListeners = Sets.newCopyOnWriteArraySet(); private final Set<ConfigListener> pluginConfigListeners = Sets.newCopyOnWriteArraySet(); private volatile TransactionConfig transactionConfig; private volatile UiConfig uiConfig; private volatile UserRecordingConfig userRecordingConfig; private volatile AdvancedConfig advancedConfig; private volatile ImmutableList<GaugeConfig> gaugeConfigs; private volatile ImmutableList<SyntheticMonitorConfig> syntheticMonitorConfigs; private volatile ImmutableList<AlertConfig> alertConfigs; private volatile ImmutableList<PluginConfig> pluginConfigs; private volatile ImmutableList<InstrumentationConfig> instrumentationConfigs; // memory barrier is used to ensure memory visibility of config values private volatile boolean memoryBarrier; public static ConfigService create(File agentDir, List<PluginDescriptor> pluginDescriptors) { ConfigService configService = new ConfigService(agentDir, pluginDescriptors); // it's nice to update config.json on startup if it is missing some/all config // properties so that the file contents can be reviewed/updated/copied if desired try { configService.writeAll(); } catch (IOException e) { logger.error(e.getMessage(), e); } return configService; } private ConfigService(File agentDir, List<PluginDescriptor> pluginDescriptors) { configFile = new ConfigFile(new File(agentDir, "config.json"), new File(agentDir, "admin.json")); this.pluginDescriptors = ImmutableList.copyOf(pluginDescriptors); TransactionConfig transactionConfig = configFile.getConfigNode("transactions", ImmutableTransactionConfig.class, mapper); if (transactionConfig == null) { this.transactionConfig = ImmutableTransactionConfig.builder().build(); } else { this.transactionConfig = transactionConfig; } UiConfig uiConfig = configFile.getConfigNode("ui", ImmutableUiConfig.class, mapper); if (uiConfig == null) { this.uiConfig = ImmutableUiConfig.builder().build(); } else { this.uiConfig = uiConfig; } UserRecordingConfig userRecordingConfig = configFile.getConfigNode("userRecording", ImmutableUserRecordingConfig.class, mapper); if (userRecordingConfig == null) { this.userRecordingConfig = ImmutableUserRecordingConfig.builder().build(); } else { this.userRecordingConfig = userRecordingConfig; } AdvancedConfig advancedConfig = configFile.getConfigNode("advanced", ImmutableAdvancedConfig.class, mapper); if (advancedConfig == null) { this.advancedConfig = ImmutableAdvancedConfig.builder().build(); } else { this.advancedConfig = advancedConfig; } List<ImmutableGaugeConfig> gaugeConfigs = configFile.getConfigNode("gauges", new TypeReference<List<ImmutableGaugeConfig>>() {}, mapper); if (gaugeConfigs == null) { this.gaugeConfigs = getDefaultGaugeConfigs(); } else { this.gaugeConfigs = ImmutableList.<GaugeConfig>copyOf(gaugeConfigs); } List<ImmutableSyntheticMonitorConfig> syntheticMonitorConfigs = configFile.getConfigNode("syntheticMonitors", new TypeReference<List<ImmutableSyntheticMonitorConfig>>() {}, mapper); if (syntheticMonitorConfigs == null) { this.syntheticMonitorConfigs = ImmutableList.of(); } else { this.syntheticMonitorConfigs = ImmutableList.<SyntheticMonitorConfig>copyOf(syntheticMonitorConfigs); } List<ImmutableAlertConfig> alertConfigs = configFile.getConfigNode("alerts", new TypeReference<List<ImmutableAlertConfig>>() {}, mapper); if (alertConfigs == null) { this.alertConfigs = ImmutableList.of(); } else { this.alertConfigs = ImmutableList.<AlertConfig>copyOf(alertConfigs); } List<ImmutablePluginConfigTemp> pluginConfigs = configFile.getConfigNode("plugins", new TypeReference<List<ImmutablePluginConfigTemp>>() {}, mapper); this.pluginConfigs = fixPluginConfigs(pluginConfigs, pluginDescriptors); List<ImmutableInstrumentationConfig> instrumentationConfigs = configFile.getConfigNode("instrumentation", new TypeReference<List<ImmutableInstrumentationConfig>>() {}, mapper); if (instrumentationConfigs == null) { this.instrumentationConfigs = ImmutableList.of(); } else { this.instrumentationConfigs = ImmutableList.<InstrumentationConfig>copyOf(instrumentationConfigs); } for (InstrumentationConfig instrumentationConfig : this.instrumentationConfigs) { instrumentationConfig.logValidationErrorsIfAny(); } } public TransactionConfig getTransactionConfig() { return transactionConfig; } public UiConfig getUiConfig() { return uiConfig; } public UserRecordingConfig getUserRecordingConfig() { return userRecordingConfig; } public AdvancedConfig getAdvancedConfig() { return advancedConfig; } public List<GaugeConfig> getGaugeConfigs() { return gaugeConfigs; } public ImmutableList<AlertConfig> getAlertConfigs() { return alertConfigs; } public ImmutableList<PluginConfig> getPluginConfigs() { return pluginConfigs; } public @Nullable PluginConfig getPluginConfig(String pluginId) { for (PluginConfig pluginConfig : pluginConfigs) { if (pluginId.equals(pluginConfig.id())) { return pluginConfig; } } return null; } public List<InstrumentationConfig> getInstrumentationConfigs() { return instrumentationConfigs; } public long getGaugeCollectionIntervalMillis() { return GAUGE_COLLECTION_INTERVAL_MILLIS; } public AgentConfig getAgentConfig() { AgentConfig.Builder builder = AgentConfig.newBuilder() .setTransactionConfig(transactionConfig.toProto()); for (GaugeConfig gaugeConfig : gaugeConfigs) { builder.addGaugeConfig(gaugeConfig.toProto()); } for (SyntheticMonitorConfig syntheticMonitorConfig : syntheticMonitorConfigs) { builder.addSyntheticMonitorConfig(syntheticMonitorConfig.toProto()); } for (AlertConfig alertConfig : alertConfigs) { builder.addAlertConfig(alertConfig.toProto()); } builder.setUiConfig(uiConfig.toProto()); for (PluginConfig pluginConfig : pluginConfigs) { builder.addPluginConfig(pluginConfig.toProto()); } for (InstrumentationConfig instrumentationConfig : instrumentationConfigs) { builder.addInstrumentationConfig(instrumentationConfig.toProto()); } builder.setUserRecordingConfig(userRecordingConfig.toProto()); builder.setAdvancedConfig(advancedConfig.toProto()); return builder.build(); } public void addConfigListener(ConfigListener listener) { configListeners.add(listener); listener.onChange(); } public void addPluginConfigListener(ConfigListener listener) { pluginConfigListeners.add(listener); } public void updateTransactionConfig(TransactionConfig updatedConfig) throws IOException { configFile.writeConfig("transactions", updatedConfig, mapper); transactionConfig = updatedConfig; notifyConfigListeners(); } public void updateGaugeConfigs(List<GaugeConfig> updatedConfigs) throws IOException { configFile.writeConfig("gauges", updatedConfigs, mapper); gaugeConfigs = ImmutableList.copyOf(updatedConfigs); notifyConfigListeners(); } public void updateSyntheticMonitorConfigs(List<SyntheticMonitorConfig> updatedConfigs) throws IOException { configFile.writeConfig("syntheticMonitors", updatedConfigs, mapper); syntheticMonitorConfigs = ImmutableList.copyOf(updatedConfigs); notifyConfigListeners(); } public void updateAlertConfigs(List<AlertConfig> updatedConfigs) throws IOException { configFile.writeConfig("alerts", updatedConfigs, mapper); alertConfigs = ImmutableList.copyOf(updatedConfigs); notifyConfigListeners(); } public void updateUiConfig(UiConfig updatedConfig) throws IOException { configFile.writeConfig("ui", updatedConfig, mapper); uiConfig = updatedConfig; notifyConfigListeners(); } public void updatePluginConfigs(List<PluginConfig> updatedConfigs) throws IOException { ImmutableList<PluginConfig> sortedConfigs = new PluginConfigOrdering().immutableSortedCopy(updatedConfigs); configFile.writeConfig("plugins", sortedConfigs, mapper); pluginConfigs = sortedConfigs; notifyAllPluginConfigListeners(); } public void updateInstrumentationConfigs(List<InstrumentationConfig> updatedConfigs) throws IOException { configFile.writeConfig("instrumentation", updatedConfigs, mapper); instrumentationConfigs = ImmutableList.copyOf(updatedConfigs); notifyConfigListeners(); } public void updateUserRecordingConfig(UserRecordingConfig updatedConfig) throws IOException { configFile.writeConfig("userRecording", updatedConfig, mapper); userRecordingConfig = updatedConfig; notifyConfigListeners(); } public void updateAdvancedConfig(AdvancedConfig updatedConfig) throws IOException { configFile.writeConfig("advanced", updatedConfig, mapper); advancedConfig = updatedConfig; notifyConfigListeners(); } public <T extends /*@NonNull*/ Object> /*@Nullable*/ T getAdminConfig(String key, Class<T> clazz) { return configFile.getAdminNode(key, clazz, mapper); } public <T extends /*@NonNull*/ Object> /*@Nullable*/ T getAdminConfig(String key, TypeReference<T> typeReference) { return configFile.getAdminNode(key, typeReference, mapper); } public void updateAdminConfig(String key, Object config) throws IOException { configFile.writeAdmin(key, config, mapper); } public void updateAdminConfigs(Map<String, Object> configs) throws IOException { configFile.writeAdmin(configs, mapper); } public boolean readMemoryBarrier() { return memoryBarrier; } public void writeMemoryBarrier() { memoryBarrier = true; } // the updated config is not passed to the listeners to avoid the race condition of multiple // config updates being sent out of order, instead listeners must call get*Config() which will // never return the updates out of order (at worst it may return the most recent update twice // which is ok) private void notifyConfigListeners() { for (ConfigListener configListener : configListeners) { configListener.onChange(); } } private void notifyAllPluginConfigListeners() { for (ConfigListener listener : pluginConfigListeners) { listener.onChange(); } writeMemoryBarrier(); } @OnlyUsedByTests public void setSlowThresholdToZero() throws IOException { transactionConfig = ImmutableTransactionConfig.copyOf(transactionConfig) .withSlowThresholdMillis(0); writeAll(); notifyConfigListeners(); } @OnlyUsedByTests public void resetConfig() throws IOException { transactionConfig = ImmutableTransactionConfig.builder() .slowThresholdMillis(0) .build(); uiConfig = ImmutableUiConfig.builder().build(); userRecordingConfig = ImmutableUserRecordingConfig.builder().build(); advancedConfig = ImmutableAdvancedConfig.builder().build(); gaugeConfigs = getDefaultGaugeConfigs(); syntheticMonitorConfigs = ImmutableList.of(); alertConfigs = ImmutableList.of(); pluginConfigs = fixPluginConfigs(ImmutableList.<ImmutablePluginConfigTemp>of(), pluginDescriptors); instrumentationConfigs = ImmutableList.of(); writeAll(); notifyConfigListeners(); notifyAllPluginConfigListeners(); } private void writeAll() throws IOException { // linked hash map to preserve ordering when writing to config file Map<String, Object> configs = Maps.newLinkedHashMap(); configs.put("transactions", transactionConfig); configs.put("ui", uiConfig); configs.put("userRecording", userRecordingConfig); configs.put("advanced", advancedConfig); configs.put("gauges", gaugeConfigs); configs.put("syntheticMonitors", syntheticMonitorConfigs); configs.put("alerts", alertConfigs); configs.put("plugins", pluginConfigs); configs.put("instrumentation", instrumentationConfigs); configFile.writeConfig(configs, mapper); } private static ImmutableList<GaugeConfig> getDefaultGaugeConfigs() { List<GaugeConfig> defaultGaugeConfigs = Lists.newArrayList(); defaultGaugeConfigs.add(ImmutableGaugeConfig.builder() .mbeanObjectName("java.lang:type=Memory") .addMbeanAttributes(ImmutableMBeanAttribute.of("HeapMemoryUsage.used", false)) .build()); defaultGaugeConfigs.add(ImmutableGaugeConfig.builder() .mbeanObjectName("java.lang:type=GarbageCollector,name=*") .addMbeanAttributes(ImmutableMBeanAttribute.of("CollectionCount", true)) .addMbeanAttributes(ImmutableMBeanAttribute.of("CollectionTime", true)) .build()); defaultGaugeConfigs.add(ImmutableGaugeConfig.builder() .mbeanObjectName("java.lang:type=MemoryPool,name=*") .addMbeanAttributes(ImmutableMBeanAttribute.of("Usage.used", false)) .build()); ImmutableGaugeConfig.Builder operatingSystemMBean = ImmutableGaugeConfig.builder() .mbeanObjectName("java.lang:type=OperatingSystem") .addMbeanAttributes(ImmutableMBeanAttribute.of("FreePhysicalMemorySize", false)); if (!JavaVersion.isJava6()) { // these are only available since 1.7 operatingSystemMBean .addMbeanAttributes(ImmutableMBeanAttribute.of("ProcessCpuLoad", false)); operatingSystemMBean .addMbeanAttributes(ImmutableMBeanAttribute.of("SystemCpuLoad", false)); } defaultGaugeConfigs.add(operatingSystemMBean.build()); return ImmutableList.copyOf(defaultGaugeConfigs); } private static ImmutableList<PluginConfig> fixPluginConfigs( @Nullable List<ImmutablePluginConfigTemp> filePluginConfigs, List<PluginDescriptor> pluginDescriptors) { // sorted by id for writing to config file List<PluginDescriptor> sortedPluginDescriptors = new PluginDescriptorOrdering().immutableSortedCopy(pluginDescriptors); Map<String, PluginConfigTemp> filePluginConfigMap = Maps.newHashMap(); if (filePluginConfigs != null) { for (ImmutablePluginConfigTemp pluginConfig : filePluginConfigs) { filePluginConfigMap.put(pluginConfig.id(), pluginConfig); } } List<PluginConfig> accuratePluginConfigs = Lists.newArrayList(); for (PluginDescriptor pluginDescriptor : sortedPluginDescriptors) { PluginConfigTemp filePluginConfig = filePluginConfigMap.get(pluginDescriptor.id()); ImmutablePluginConfig.Builder builder = ImmutablePluginConfig.builder() .pluginDescriptor(pluginDescriptor); for (PropertyDescriptor propertyDescriptor : pluginDescriptor.properties()) { builder.putProperties(propertyDescriptor.name(), getPropertyValue(filePluginConfig, propertyDescriptor)); } accuratePluginConfigs.add(builder.build()); } return ImmutableList.copyOf(accuratePluginConfigs); } private static PropertyValue getPropertyValue(@Nullable PluginConfigTemp pluginConfig, PropertyDescriptor propertyDescriptor) { if (pluginConfig == null) { return propertyDescriptor.getValidatedNonNullDefaultValue(); } PropertyValue propertyValue = getValidatedPropertyValue(pluginConfig.properties(), propertyDescriptor.name(), propertyDescriptor.type()); if (propertyValue == null) { return propertyDescriptor.getValidatedNonNullDefaultValue(); } return propertyValue; } private static @Nullable PropertyValue getValidatedPropertyValue( Map<String, PropertyValue> properties, String propertyName, PropertyType propertyType) { PropertyValue propertyValue = properties.get(propertyName); if (propertyValue == null) { return null; } Object value = propertyValue.value(); if (value == null) { return PropertyValue.getDefaultValue(propertyType); } if (PropertyDescriptor.isValidType(value, propertyType)) { return propertyValue; } else { logger.warn("invalid value for plugin property: {}", propertyName); return PropertyValue.getDefaultValue(propertyType); } } private static class PluginDescriptorOrdering extends Ordering<PluginDescriptor> { @Override public int compare(PluginDescriptor left, PluginDescriptor right) { return left.id().compareToIgnoreCase(right.id()); } } private static class PluginConfigOrdering extends Ordering<PluginConfig> { @Override public int compare(PluginConfig left, PluginConfig right) { return left.id().compareToIgnoreCase(right.id()); } } @Value.Immutable interface PluginConfigTemp { String id(); Map<String, PropertyValue> properties(); } }