All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.agent.config.ConfigService Maven / Gradle / Ivy

There is a newer version: 0.9.24
Show newest version
/*
 * Copyright 2011-2016 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 org.glowroot.agent.shaded.fasterxml.jackson.core.JsonProcessingException;
import org.glowroot.agent.shaded.fasterxml.jackson.core.type.TypeReference;
import org.glowroot.agent.shaded.fasterxml.jackson.databind.ObjectMapper;
import org.glowroot.agent.shaded.google.common.base.Joiner;
import org.glowroot.agent.shaded.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.google.common.collect.Lists;
import org.glowroot.agent.shaded.google.common.collect.Maps;
import org.glowroot.agent.shaded.google.common.collect.Ordering;
import org.glowroot.agent.shaded.google.common.collect.Sets;
import org.immutables.value.Value;
import org.glowroot.agent.shaded.slf4j.Logger;
import org.glowroot.agent.shaded.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.agent.shaded.glowroot.common.util.ObjectMappers;
import org.glowroot.agent.shaded.glowroot.common.util.OnlyUsedByTests;
import org.glowroot.agent.shaded.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 pluginDescriptors;

    private final Set configListeners = Sets.newCopyOnWriteArraySet();
    private final Set pluginConfigListeners = Sets.newCopyOnWriteArraySet();

    private volatile TransactionConfig transactionConfig;
    private volatile UiConfig uiConfig;
    private volatile UserRecordingConfig userRecordingConfig;
    private volatile AdvancedConfig advancedConfig;
    private volatile ImmutableList gaugeConfigs;
    private volatile ImmutableList alertConfigs;
    private volatile ImmutableList pluginConfigs;
    private volatile ImmutableList instrumentationConfigs;

    // memory barrier is used to ensure memory visibility of config values
    private volatile boolean memoryBarrier;

    public static ConfigService create(File baseDir, List pluginDescriptors) {
        ConfigService configService = new ConfigService(baseDir, 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 baseDir, List pluginDescriptors) {
        configFile =
                new ConfigFile(new File(baseDir, "config.json"), new File(baseDir, "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 gaugeConfigs = configFile.getConfigNode("gauges",
                new TypeReference>() {}, mapper);
        if (gaugeConfigs == null) {
            this.gaugeConfigs = getDefaultGaugeConfigs();
        } else {
            this.gaugeConfigs = ImmutableList.copyOf(gaugeConfigs);
        }
        List alertConfigs = configFile.getConfigNode("alerts",
                new TypeReference>() {}, mapper);
        if (alertConfigs == null) {
            this.alertConfigs = ImmutableList.of();
        } else {
            this.alertConfigs = ImmutableList.copyOf(alertConfigs);
        }
        List pluginConfigs = configFile.getConfigNode("plugins",
                new TypeReference>() {}, mapper);
        this.pluginConfigs = fixPluginConfigs(pluginConfigs, pluginDescriptors);

        List instrumentationConfigs =
                configFile.getConfigNode("instrumentation",
                        new TypeReference>() {}, mapper);
        if (instrumentationConfigs == null) {
            this.instrumentationConfigs = ImmutableList.of();
        } else {
            this.instrumentationConfigs =
                    ImmutableList.copyOf(instrumentationConfigs);
        }

        for (InstrumentationConfig instrumentationConfig : this.instrumentationConfigs) {
            ImmutableList errors = instrumentationConfig.validationErrors();
            if (!errors.isEmpty()) {
                StringBuilder sb = new StringBuilder();
                sb.append("Invalid instrumentation config: ");
                sb.append(Joiner.on(", ").join(errors));
                sb.append(" ");
                try {
                    sb.append(ObjectMappers.create().writeValueAsString(instrumentationConfig));
                } catch (JsonProcessingException e) {
                    logger.error(e.getMessage(), e);
                }
                logger.error(sb.toString());
            }
        }
    }

    public TransactionConfig getTransactionConfig() {
        return transactionConfig;
    }

    public UiConfig getUiConfig() {
        return uiConfig;
    }

    public UserRecordingConfig getUserRecordingConfig() {
        return userRecordingConfig;
    }

    public AdvancedConfig getAdvancedConfig() {
        return advancedConfig;
    }

    public List getGaugeConfigs() {
        return gaugeConfigs;
    }

    public List getAlertConfigs() {
        return alertConfigs;
    }

    public ImmutableList getPluginConfigs() {
        return pluginConfigs;
    }

    public @Nullable PluginConfig getPluginConfig(String pluginId) {
        for (PluginConfig pluginConfig : pluginConfigs) {
            if (pluginId.equals(pluginConfig.id())) {
                return pluginConfig;
            }
        }
        return null;
    }

    public List 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 (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 updatedConfigs) throws IOException {
        configFile.writeConfig("gauges", updatedConfigs, mapper);
        gaugeConfigs = ImmutableList.copyOf(updatedConfigs);
        notifyConfigListeners();
    }

    public void updateAlertConfigs(List 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 updatedConfigs) throws IOException {
        ImmutableList sortedConfigs =
                new PluginConfigOrdering().immutableSortedCopy(updatedConfigs);
        configFile.writeConfig("plugins", sortedConfigs, mapper);
        pluginConfigs = sortedConfigs;
        notifyAllPluginConfigListeners();
    }

    public void updateInstrumentationConfigs(List 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  /*@Nullable*/ T getAdmin(String key, Class clazz) {
        return configFile.getAdminNode(key, clazz, mapper);
    }

    public  /*@Nullable*/ T getAdminConfig(String key,
            TypeReference 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 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 resetAllConfig() throws IOException {
        transactionConfig = ImmutableTransactionConfig.builder()
                .slowThresholdMillis(0)
                .build();
        userRecordingConfig = ImmutableUserRecordingConfig.builder().build();
        advancedConfig = ImmutableAdvancedConfig.builder().build();
        pluginConfigs =
                fixPluginConfigs(ImmutableList.of(), pluginDescriptors);
        gaugeConfigs = getDefaultGaugeConfigs();
        instrumentationConfigs = ImmutableList.of();
        writeAll();
        notifyConfigListeners();
        notifyAllPluginConfigListeners();
    }

    private void writeAll() throws IOException {
        // linked hash map to preserve ordering when writing to config file
        Map 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("alerts", alertConfigs);
        configs.put("plugins", pluginConfigs);
        configs.put("instrumentation", instrumentationConfigs);
        configFile.writeConfig(configs, mapper);
    }

    private static ImmutableList getDefaultGaugeConfigs() {
        List 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 fixPluginConfigs(
            @Nullable List filePluginConfigs,
            List pluginDescriptors) {

        // sorted by id for writing to config file
        List sortedPluginDescriptors =
                new PluginDescriptorOrdering().immutableSortedCopy(pluginDescriptors);

        Map filePluginConfigMap = Maps.newHashMap();
        if (filePluginConfigs != null) {
            for (ImmutablePluginConfigTemp pluginConfig : filePluginConfigs) {
                filePluginConfigMap.put(pluginConfig.id(), pluginConfig);
            }
        }

        List 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 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);
        }
    }

    public static class ShadeProtectedTypeReference
            extends TypeReference {}

    private static class PluginDescriptorOrdering extends Ordering {
        @Override
        public int compare(PluginDescriptor left, PluginDescriptor right) {
            return left.id().compareToIgnoreCase(right.id());
        }
    }

    private static class PluginConfigOrdering extends Ordering {
        @Override
        public int compare(PluginConfig left, PluginConfig right) {
            return left.id().compareToIgnoreCase(right.id());
        }
    }

    @Value.Immutable
    interface PluginConfigTemp {
        String id();
        Map properties();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy