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

org.graylog.metrics.prometheus.mapping.PrometheusMappingFilesHandler Maven / Gradle / Ivy

There is a newer version: 6.0.2
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog.metrics.prometheus.mapping;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Resources;
import io.prometheus.client.dropwizard.samplebuilder.MapperConfig;
import org.graylog.metrics.prometheus.PrometheusExporterConfiguration;
import org.graylog2.plugin.utilities.FileInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

public class PrometheusMappingFilesHandler {
    private static final Logger LOG = LoggerFactory.getLogger(PrometheusMappingFilesHandler.class);
    static final String CORE_MAPPING_RESOURCE = "prometheus-exporter.yml";

    @Nullable
    private final Path coreMappingFile;
    @Nullable
    private final Path customMappingFile;
    private final String coreMappingResource;
    private final PrometheusMappingConfigLoader mapperConfigLoader;

    private final AtomicReference coreMappingFileInfoRef = new AtomicReference<>(null);
    private final AtomicReference customMappingFileInfoRef = new AtomicReference<>(null);

    @Inject
    public PrometheusMappingFilesHandler(@Named(PrometheusExporterConfiguration.MAPPING_FILE_PATH_CORE) @Nullable Path coreMappingPath,
                                         @Named(PrometheusExporterConfiguration.MAPPING_FILE_PATH_CUSTOM) @Nullable Path customMappingPath,
                                         PrometheusMappingConfigLoader mapperConfigLoader) {
        this(coreMappingPath, customMappingPath, CORE_MAPPING_RESOURCE, mapperConfigLoader);
    }

    @VisibleForTesting
    PrometheusMappingFilesHandler(@Named(PrometheusExporterConfiguration.MAPPING_FILE_PATH_CORE) @Nullable Path coreMappingPath,
                                  @Named(PrometheusExporterConfiguration.MAPPING_FILE_PATH_CUSTOM) @Nullable Path customMappingPath,
                                  String coreMappingResource,
                                  PrometheusMappingConfigLoader mapperConfigLoader) {
        this.coreMappingFile = buildPath(coreMappingPath, coreMappingFileInfoRef);
        this.customMappingFile = buildPath(customMappingPath, customMappingFileInfoRef);
        this.coreMappingResource = coreMappingResource;
        this.mapperConfigLoader = mapperConfigLoader;
    }

    private static Path buildPath(Path file, AtomicReference fileInfoRef) {
        if (file != null) {
            if (Files.exists(file)) {
                fileInfoRef.set(FileInfo.forPath(file));
            }
            return file.toAbsolutePath();
        }
        return null;
    }

    private Optional detectChange(@Nullable Path file, AtomicReference fileInfoRef) {
        if (file != null && Files.exists(file)) {
            LOG.debug("Detecting changes for file <{}>", file);

            if (fileInfoRef.get() == null) {
                LOG.debug("Getting initial file info for file <{}>", file);
                final FileInfo newFileInfo = FileInfo.forPath(file);
                fileInfoRef.set(newFileInfo);
                // The file didn't exist before so we want to trigger a change
                return Optional.of(new FileInfo.Change(newFileInfo));
            }

            final FileInfo.Change change = fileInfoRef.get().checkForChange();

            if (change.isChanged()) {
                LOG.debug("Updating file info for changed file <{}>", file);
                // Use latest file info to make update check correct on next call
                fileInfoRef.set(change.fileInfo());
            }

            return Optional.of(change);
        }

        LOG.debug("File <{}> doesn't exist", file);
        return Optional.empty();
    }

    public boolean filesHaveChanged() {
        if ((coreMappingFile == null || !Files.exists(coreMappingFile)) &&
                (customMappingFile == null || !Files.exists(customMappingFile))) {
            return false;
        }

        final List changes = new ArrayList<>();

        // Synchronize to ensure atomic update of the file info fields
        synchronized (this) {
            detectChange(coreMappingFile, coreMappingFileInfoRef).ifPresent(changes::add);
            detectChange(customMappingFile, customMappingFileInfoRef).ifPresent(changes::add);
        }

        return changes.stream().anyMatch(FileInfo.Change::isChanged);
    }

    public List getMapperConfigs() {
        final Set mapperConfigs = new HashSet<>();

        // If an external core mapping file exists it takes precedence over the included mapping resource
        if (coreMappingFile != null && Files.exists(coreMappingFile)) {
            LOG.debug("Loading core metric mappings from file <{}>", coreMappingFile);
            try {
                final InputStream inputStream = Files.newInputStream(coreMappingFile, StandardOpenOption.READ);
                mapperConfigs.addAll(mapperConfigLoader.load(inputStream));
            } catch (IOException e) {
                LOG.error("Couldn't load mapping from file <{}>", coreMappingFile, e);
            }
        } else {
            LOG.debug("Loading core metric mappings from resource <{}>", coreMappingResource);
            try {
                mapperConfigs.addAll(mapperConfigLoader.load(Resources.getResource(coreMappingResource).openStream()));
            } catch (IOException e) {
                LOG.error("Couldn't load mapping from resource <{}>", coreMappingResource, e);
            }
        }

        // Load custom mappings if they exist. Custom mappings cannot override core mappings!
        if (customMappingFile != null && Files.exists(customMappingFile)) {
            LOG.debug("Loading custom metric mappings from file <{}>", customMappingFile);
            try {
                final Set coreMetricNames = mapperConfigs.stream()
                        .map(MapperConfig::getName)
                        .collect(Collectors.toSet());

                final InputStream inputStream = Files.newInputStream(customMappingFile, StandardOpenOption.READ);
                final Set customConfigs = mapperConfigLoader.load(inputStream)
                        .stream()
                        .filter(config -> {
                            if (coreMetricNames.contains(config.getName())) {
                                LOG.warn("Custom metric mapping config cannot overwrite core metric: {}", config.getName());
                                return false;
                            }
                            return true;
                        })
                        .collect(Collectors.toSet());

                mapperConfigs.addAll(customConfigs);
            } catch (IOException e) {
                LOG.error("Couldn't load mapping from file <{}>", coreMappingFile, e);
            }
        }

        return ImmutableList.copyOf(mapperConfigs);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy