public class CircuitBreakerMetricsGenerator extends MetricsGenerator<AllCircuitBreakerStats> {
    private static final Logger logger = Loggers.getLogger(CircuitBreakerMetricsGenerator.class, "init");
    private static final String LABEL_NAME = "circuit_name";

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, AllCircuitBreakerStats allStats) {
        logger.debug("Generating data about circuit breaker: {}", allStats);

        SingleValueWriter circuitBreakerLimit = writer
                .addGauge("es_breaker_limit_bytes")
                .withHelp("Memory limit of circuit breaker in bytes");

        SingleValueWriter circuitBreakerEstimated = writer
                .addGauge("es_breaker_estimated_bytes")
                .withHelp("Estimated memory of circuit breaker in bytes");

        SingleValueWriter circuitBreakerTripped = writer
                .addCounter("es_breaker_tripped")
                .withHelp("Counter of how many times circuit breaker tripped");


        SingleValueWriter circuitBreakerOverhead = writer.addGauge("es_breaker_overhead")
                .withHelp("Overhead factor for circuit breaker");

        for (CircuitBreakerStats cbStats : allStats.getAllStats()) {
            String name = cbStats.getName();

            circuitBreakerEstimated.value(cbStats.getEstimated(), LABEL_NAME, name);
            circuitBreakerLimit.value(cbStats.getLimit(), LABEL_NAME, name);
            circuitBreakerTripped.value(cbStats.getTrippedCount(), LABEL_NAME, name);
            circuitBreakerOverhead.value(cbStats.getOverhead(), LABEL_NAME, name);
        }
        return writer;
    }
} public class ClusterHealthMetricsGenerator extends MetricsGenerator<ClusterHealthResponse> {
    private static final Logger logger = Loggers.getLogger(ClusterHealthMetricsGenerator.class, "init");
    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, ClusterHealthResponse clusterHealth) {
        logger.debug("Generating data about cluster health: {}", clusterHealth);

        //Shards global data
        writer.addGauge("es_active_shards")
                .withHelp("Number of active shards")
                .value(clusterHealth.getActiveShards());

        writer.addGauge("es_active_primary_shards")
                .withHelp("Number of active primary")
                .value(clusterHealth.getActivePrimaryShards());

        writer.addGauge("es_relocating_shards")
                .withHelp("Number of relocating shards")
                .value(clusterHealth.getRelocatingShards());

        writer.addGauge("es_initializing_shards")
                .withHelp("Number of initializing shards")
                .value(clusterHealth.getInitializingShards());

        writer.addGauge("es_unassigned_shards")
                .withHelp("Number of unassigned shards")
                .value(clusterHealth.getUnassignedShards());

        writer.addGauge("es_shards_count")
                .withHelp("Number of shards in a cluster")
                .value(clusterHealth.getActiveShards(), "type", "active")
                .value(clusterHealth.getActivePrimaryShards(), "type", "primary")
                .value(clusterHealth.getRelocatingShards(), "type", "relocating")
                .value(clusterHealth.getInitializingShards(), "type", "initializing")
                .value(clusterHealth.getUnassignedShards(), "type", "unassigned")
                .value(clusterHealth.getDelayedUnassignedShards(), "type", "delayed-unassigned");

        writer.addGauge("es_shards_active_percentage")
                .withHelp("Percentage of active shards")
                .value(clusterHealth.getActiveShardsPercent());

        //Nodes data
        writer.addGauge("es_nodes")
                .withHelp("Number of nodes in a cluster")
                .value(clusterHealth.getNumberOfNodes());

        writer.addGauge("es_data_nodes")
                .withHelp("Number of data nodes in a cluster")
                .value(clusterHealth.getNumberOfDataNodes());

        //Tasks data
        writer.addGauge("es_pending_tasks")
                .withHelp("Number of tasks pending")
                .value(clusterHealth.getNumberOfPendingTasks());

        //Tasks data
        writer.addGauge("es_pending_inflight_fetch")
                .withHelp("Number of in-flight fetch")
                .value(clusterHealth.getNumberOfInFlightFetch());

        //Cluster status
        writer.addGauge("es_status")
                .withHelp("Cluster status")
                .value(clusterHealth.getStatus().value());

        writer.addGauge("es_status_type")
                .withHelp("Cluster status per type")
                .value(1, "status", clusterHealth.getStatus().name());

        //Status for each index
        SingleValueWriter es_index_active_shards = writer.addGauge("es_index_active_shards")
                .withHelp("Number of active shards assigned to index");

        SingleValueWriter es_index_active_primary_shards = writer.addGauge("es_index_active_primary_shards")
                .withHelp("Number of active primary shards assigned to index");

        SingleValueWriter es_index_initializing_shards = writer.addGauge("es_index_initializing_shards")
                .withHelp("Number of initializing shards assigned to index");

        SingleValueWriter es_index_relocating_shards = writer.addGauge("es_index_relocating_shards")
                .withHelp("Number of initializing shards assigned to index");

        SingleValueWriter es_index_unassigned_shards = writer.addGauge("es_index_unassigned_shards")
                .withHelp("Number of initializing shards assigned to index");

        SingleValueWriter es_index_status = writer.addGauge("es_index_status")
                .withHelp("Status of index");

        SingleValueWriter es_index_status_type = writer.addGauge("es_index_status_type")
                .withHelp("Inde status per type");

        for (Map.Entry<String, ClusterIndexHealth> indexStatusEntry : clusterHealth.getIndices().entrySet()) {
            String indexName = indexStatusEntry.getKey();
            ClusterIndexHealth indexStatus = indexStatusEntry.getValue();

            es_index_active_shards.value(indexStatus.getActiveShards(), "index", indexName);
            es_index_active_primary_shards.value(indexStatus.getActivePrimaryShards(), "index", indexName);
            es_index_initializing_shards.value(indexStatus.getInitializingShards(), "index", indexName);
            es_index_relocating_shards.value(indexStatus.getRelocatingShards(), "index", indexName);
            es_index_unassigned_shards.value(indexStatus.getUnassignedShards(), "index", indexName);
            es_index_status.value(indexStatus.getStatus().value(), "index", indexName);
            es_index_status_type.value(1, "index", indexName, "status", indexStatus.getStatus().name());
        }

        return writer;
    }
} public class ClusterStateMetricsGenerator extends MetricsGenerator<ClusterState> {
    private static final Logger logger = Loggers.getLogger(ClusterStateMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, ClusterState clusterState) {
        logger.debug("Generating data about cluster state: {}", clusterState);

        SingleValueWriter persistentGauge = writer.addGauge("es_cluster_persistent_settings")
                .withHelp("Cluster persistent settings value visible from this node");
        fillSettings(persistentGauge, clusterState.getMetaData().persistentSettings());

        SingleValueWriter transientGauge = writer.addGauge("es_cluster_transient_settings")
                .withHelp("Cluster persistent settings value visible from this node");
        fillSettings(transientGauge, clusterState.getMetaData().transientSettings());

        SingleValueWriter settingsGauge = writer.addGauge("es_cluster_settings")
                .withHelp("Cluster effective settings value visible from this node");
        fillSettings(settingsGauge, clusterState.getMetaData().settings());

        return writer;
    }

    private void fillSettings(SingleValueWriter settingsGauge, Settings settings) {
        settings.keySet().forEach((key) -> {
            settingsGauge.value(1, key, settings.get(key));
        });
    }
} public class FsMetricsGenerator extends MetricsGenerator<FsInfo> {
    private static final Logger logger = Loggers.getLogger(FsMetricsGenerator.class, "init");
    private static final String PATH_LABEL = "path";

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, FsInfo fsInfo) {
        logger.debug("Generating data about FS stats: {}", fsInfo);

        writer.addGauge("es_fs_total_bytes")
                .withHelp("Total size of ES storage")
                .value(fsInfo.getTotal().getTotal().getBytes());
        writer.addGauge("es_fs_available_bytes")
                .withHelp("Available size of ES storage")
                .value(fsInfo.getTotal().getAvailable().getBytes());
        writer.addGauge("es_fs_free_bytes")
                .withHelp("Free size of ES storage")
                .value(fsInfo.getTotal().getFree().getBytes());

        SingleValueWriter pathTotal = writer.addGauge("es_fs_path_total_bytes")
                .withHelp("Total size of ES storage");
        SingleValueWriter pathAvailable = writer.addGauge("es_fs_path_available_bytes")
                .withHelp("Available size of ES storage");
        SingleValueWriter pathFree = writer.addGauge("es_fs_path_free_bytes")
                .withHelp("Free size of ES storage");

        for (Path pathStats : fsInfo) {
            String path = pathStats.getPath();

            pathTotal.value(pathStats.getTotal().getBytes(), PATH_LABEL, path);
            pathAvailable.value(pathStats.getAvailable().getBytes(), PATH_LABEL, path);
            pathFree.value(pathStats.getFree().getBytes(), PATH_LABEL, path);
        }
        return writer;
    }
} public class HttpMetricsGenerator extends MetricsGenerator<HttpStats> {
    private static final Logger logger = Loggers.getLogger(HttpMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, HttpStats httpStats) {
        writer.addGauge("es_http_server_open")
            .withHelp("Number of opened HTTP connection")
            .value(httpStats.getServerOpen());
        writer.addGauge("es_http_total_open")
                .withHelp("Number of totally opened HTTP connection")
                .value(httpStats.getTotalOpen());

        return writer;
    }
} public class IndicesMetricsGenerator extends  MetricsGenerator<NodeIndicesStats> {
    private static final Logger logger = Loggers.getLogger(IndicesMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, NodeIndicesStats indicesStats) {
        logger.debug("Generating output based on indicies stats: {}", indicesStats);

        writeDocsStats(writer, indicesStats.getDocs());
        writeStoreStats(writer, indicesStats.getStore());
        writeIndexingStats(writer, indicesStats.getIndexing());
        writeFeldsDataStats(writer, indicesStats.getFieldData());
        writeQueryCacheStats(writer, indicesStats.getQueryCache());
        writeRecoveryStats(writer, indicesStats.getRecoveryStats());
        writeSearchStats(writer, indicesStats.getSearch());
        writeTransLogStats(writer, indicesStats.getTranslog());

        writer.addSummary("es_flush")
                .withHelp("Summary of flush operations")
                .summary(indicesStats.getFlush().getTotal(), indicesStats.getFlush().getTotalTimeInMillis());
        writer.addSummary("es_refresh")
                .withHelp("Summary of refresh operations")
                .summary(indicesStats.getRefresh().getTotal(), indicesStats.getRefresh().getTotalTimeInMillis());
        writer.addSummary("es_merge")
                .withHelp("Summary of merge operations")
                .summary(indicesStats.getMerge().getTotal(), indicesStats.getMerge().getTotalTimeInMillis());
        writer.addSummary("es_merge_docs")
                .withHelp("Summary of merge operations per doc")
                .summary(indicesStats.getMerge().getTotalNumDocs(), indicesStats.getMerge().getTotalTimeInMillis());
        return writer;
    }

    private void writeTransLogStats(PrometheusFormatWriter writer, TranslogStats translog) {
        writer.addGauge("es_translog_size_bytes")
                .withHelp("Size of translog queue")
                .value(translog.getTranslogSizeInBytes());

        writer.addGauge("es_translog_uncommited_opertations")
                .withHelp("Number of uncommited operations")
                .value(translog.getUncommittedOperations());

        writer.addGauge("es_translog_uncommited_size_bytes")
                .withHelp("Size of uncommited operations")
                .value(translog.getUncommittedSizeInBytes());

        writer.addGauge("es_translog_earliest_last_modified")
                .withHelp("Age of earliest last modified document")
                .value(translog.getEarliestLastModifiedAge());
    }

    private void writeSearchStats(PrometheusFormatWriter writer, SearchStats search) {
        writer.addGauge("es_search_query_current")
                .withHelp("Number of search queries executed in cluster")
                .value(search.getTotal().getQueryCurrent());
        writer.addSummary("es_search_query")
                .withHelp("Total number of search queries executed in cluster")
                .summary(search.getTotal().getQueryCount(), search.getTotal().getQueryTimeInMillis());
    }

    private void writeRecoveryStats(PrometheusFormatWriter writer, RecoveryStats recoveryStats) {
        writer.addGauge("es_recovery_source")
                .withHelp("Number of ongoing recoveries for which a shard serves as a source")
                .value(recoveryStats.currentAsSource());

        writer.addGauge("es_recovery_target")
                .withHelp("Number of ongoing recoveries for which a shard serves as a target")
                .value(recoveryStats.currentAsTarget());

        writer.addGauge("es_recovery_throttletime_ms")
                .withHelp("Throttle time of pending recovery")
                .value(recoveryStats.throttleTime().millis());
    }

    private void writeQueryCacheStats(PrometheusFormatWriter writer, QueryCacheStats queryCache) {
        writer.addGauge("es_querycache_size")
                .withHelp("Number of documents that's are in the cache")
                .value(queryCache.getCacheSize());

        writer.addCounter("es_querycache_count")
                .withHelp("Number of documents cached")
                .value(queryCache.getCacheCount());

        writer.addCounter("es_querycache_evictions")
                .withHelp("Number of evicted document")
                .value(queryCache.getEvictions());

        writer.addCounter("es_querycache_hitcount")
                .withHelp("Number of documents found in a cache")
                .value(queryCache.getHitCount());

        writer.addGauge("es_querycache_memory_bytes")
                .withHelp("Size of query cache in memory")
                .value(queryCache.getMemorySizeInBytes());
    }

    private void writeFeldsDataStats(PrometheusFormatWriter writer, FieldDataStats fieldData) {
        writer.addGauge("es_fielddata_size_bytes")
                .withHelp("Size of total fielddata size")
                .value(fieldData.getMemorySizeInBytes());

        writer.addGauge("es_fielddata_eviction_count")
                .withHelp("Number of evictions in fielddata")
                .value(fieldData.getEvictions());
    }

    private void writeIndexingStats(PrometheusFormatWriter writer, IndexingStats indexingStats) {
        logger.debug("Dumping data from indexes: {}", indexingStats.getTotal());

        IndexingStats.Stats totalStats = indexingStats.getTotal();
        writer.addGauge("es_indexing_current")
                .withHelp("Number of active index operations")
                .value(totalStats.getIndexCurrent());
        writer.addCounter("es_indexing_failed_count")
                    .withHelp("Counter of failed indexing operations")
                    .value(totalStats.getIndexFailedCount());
        writer.addCounter("es_indexing_delete_count")
                    .withHelp("Number of delete operations")
                    .value(totalStats.getDeleteCount());
        writer.addGauge("es_indexing_delete_current")
                    .withHelp("Number of active delete operations")
                    .value(totalStats.getDeleteCurrent());
        writer.addGauge("es_indexng_isthrottled")
                    .withHelp("Flag to check is node throttled")
                    .value(totalStats.isThrottled()? 1.0 : 0.0);
        writer.addSummary("es_indexing")
                .withHelp("Indexing latency")
                .summary(totalStats.getIndexCount(), totalStats.getIndexTime().millis());
        writer.addSummary("es_delete")
                .withHelp("Delete latency")
                .summary(totalStats.getDeleteCount(), totalStats.getDeleteTime().millis());
    }

    private void writeDocsStats(PrometheusFormatWriter writer, DocsStats docsStats) {
        writer.addCounter("es_common_docs_count")
                .withHelp("Elasticsearch documents counter")
                .value(docsStats.getCount());
        writer.addCounter("es_common_docs_deleted_count")
                .withHelp("Elasticsearch documents deleted")
                .value(docsStats.getDeleted());
        writer.addGauge("es_common_docs_size_bytes")
                .withHelp("Size in bytes occupied by all docs")
                .value(getDynamicValue(docsStats, "getTotalSizeInBytes"));
    }

    private void writeStoreStats(PrometheusFormatWriter writer, StoreStats storeStats) {
        writer.addGauge("es_common_store_size")
                .withHelp("Elasticsearch storage size (in bytes)")
                .value(storeStats.getSizeInBytes());
    }
} public class IngestMetricsGenerator extends MetricsGenerator<IngestStats> {
    private static final Logger logger = Loggers.getLogger(IngestMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, IngestStats ingestStats) {
        logger.debug("Generating metrics about ingest stats: {}", ingestStats);

        writer.addCounter("es_ingest_total_count")
                .withHelp("Total number of processed documents during ingestion phase")
                .value(ingestStats.getTotalStats().getIngestCount());

        writer.addCounter("es_ingest_total_time_seconds")
                .withHelp("Total time spend on processed documents during ingestion phase")
                .value(ingestStats.getTotalStats().getIngestTimeInMillis() / 1000);

        writer.addGauge("es_ingest_total_current")
                .withHelp("The total number of ingest preprocessing operations that have failed")
                .value(ingestStats.getTotalStats().getIngestCurrent());

        writer.addGauge("es_ingest_total_failed_count")
                .withHelp("The total number of ingest preprocessing operations that have failed")
                .value(ingestStats.getTotalStats().getIngestFailedCount());


        SingleValueWriter es_ingest_pipeline_count = writer.addCounter("es_ingest_pipeline_count")
                .withHelp("Total number of processed documents during ingestion phase in pipeline");

        SingleValueWriter es_ingest_pipeline_time_seconds = writer.addCounter("es_ingest_pipeline_time_seconds")
                .withHelp("Total time spend on processed documents during ingestion phase in pipeline");

        SingleValueWriter es_ingest_pipeline_current = writer.addGauge("es_ingest_pipeline_current")
                .withHelp("The total number of ingest preprocessing operations that have failed in pipeline");

        SingleValueWriter es_ingest_pipeline_failed_count = writer.addGauge("es_ingest_pipeline_failed_count")
                .withHelp("The total number of ingest preprocessing operations that have failed in pipeline");

        for (IngestStats.PipelineStat entry : ingestStats.getPipelineStats()) {
            String pipeline = entry.getPipelineId();
            IngestStats.Stats stats = entry.getStats();

            es_ingest_pipeline_count.value(stats.getIngestCount(), "pipeline", pipeline);
            es_ingest_pipeline_time_seconds.value(stats.getIngestTimeInMillis() / 1000, "pipeline", pipeline);
            es_ingest_pipeline_current.value(stats.getIngestCurrent(), "pipeline", pipeline);
            es_ingest_pipeline_failed_count.value(stats.getIngestFailedCount(), "pipeline", pipeline);
        }

        return writer;
    }
} public class JvmMetricsGenerator extends MetricsGenerator<JvmStats> {
    private static final Logger logger = Loggers.getLogger(PrometheusExporterPlugin.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, JvmStats jvmStats) {
        logger.debug("Generating output for JVM stats: {}", jvmStats);

        logger.debug("Now memory: {}", jvmStats.getMem());
        writer.addGauge("jvm_memory_bytes_used")
                .withHelp("Used bytes of a given JVM memory area.")
                .value(jvmStats.getMem().getHeapUsed().getBytes(), "area", "heap")
                .value(jvmStats.getMem().getNonHeapUsed().getBytes(), "area", "nonheap");


        writer.addGauge("jvm_memory_bytes_committed")
                .withHelp("Committed (bytes) of a given JVM memory area")
                .value(jvmStats.getMem().getHeapCommitted().getBytes(), "area", "heap")
                .value(jvmStats.getMem().getNonHeapCommitted().getBytes(), "area", "nonheap");

        writer.addGauge("jvm_memory_bytes_max")
                .withHelp("Max (bytes) of a given JVM memory area.")
                .value(jvmStats.getMem().getHeapMax().getBytes(), "area", "heap");


        //JVM threads
        logger.debug("Now threads: {}", jvmStats.getThreads());
        writer.addGauge("jvm_threads_current")
                .withHelp("Current thread count of a JVM")
                .value(jvmStats.getThreads().getCount());

        writer.addGauge("jvm_threads_peak")
                .withHelp("Peak thread count of a JVM")
                .value(jvmStats.getThreads().getPeakCount());

        //JVM classes
        logger.debug("Now classes: {}", jvmStats.getClasses());
        writer.addGauge("jvm_classes_loaded")
                .withHelp("The number of classes that are currently loaded in the JVM")
                .value(jvmStats.getClasses().getLoadedClassCount());

        writer.addCounter("jvm_classes_loaded_total")
                .withHelp("The total number of classes that have been loaded since the JVM has started execution")
                .value(jvmStats.getClasses().getTotalLoadedClassCount());

        writer.addCounter("jvm_classes_unloaded_total")
                .withHelp("The total number of classes that have been unloaded since the JVM has started execution")
                .value(jvmStats.getClasses().getUnloadedClassCount());

        SummaryValueWriter gcValueWriter = writer.addSummary("jvm_gc_collection_seconds")
                .withHelp("The total number of seconds spend on GC collection");

        for (GarbageCollector collector : jvmStats.getGc().getCollectors()) {
            gcValueWriter.summary(collector.getCollectionCount(), collector.getCollectionTime().getMillis(), "name", collector.getName());
        }
        
        //ES custom fields
        logger.debug("Now custom: {}", jvmStats.getClasses());

        writer.addGauge("es_jvm_timestamp")
                .withHelp("Timestamp of last JVM status scrap")
                .value(jvmStats.getTimestamp());

        writer.addGauge("es_jvm_uptime")
                .withHelp("Node uptime in millis")
                .value(jvmStats.getUptime().millis());

        writer.addGauge("es_jvm_memory_heap_used_percent")
                .withHelp("Heap memory of JVM (in percentage)")
                .value(jvmStats.getMem().getHeapUsedPercent());

        return writer;
    }
} public class NodeUsageGenerator extends MetricsGenerator<NodeUsage> {
    private static final Logger logger = Loggers.getLogger(NodeUsageGenerator.class, "init");
    private static final String ENDPOINT_LABEL = "endpoint";

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, NodeUsage nodeUsage) {
        logger.debug("Generating output for REST usage: {}", nodeUsage);

        SingleValueWriter restActions = writer.addCounter("es_rest_count")
                .withHelp("Number of REST endpoint executions");

        nodeUsage.getRestUsage()
                .forEach((key, value) -> restActions.value(value, ENDPOINT_LABEL, key));
        return writer;
    }
} public class OsMetricsGenerator extends MetricsGenerator<OsStats> {
    private static final Logger logger = Loggers.getLogger(OsMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, OsStats osStats) {
        logger.debug("Generating output for OS stats: {}", osStats);

        writer.addGauge("es_cpu_percentage")
                .withHelp("ElasticSearch CPU percentage")
                .value(osStats.getCpu().getPercent());

        writer.addGauge("es_cpu_loadavg")
                .withHelp("Elasticsearch CPU loadavg")
                .value(osStats.getCpu().getLoadAverage()[0],"loadavg", "1m")
                .value(osStats.getCpu().getLoadAverage()[1],"loadavg", "5m")
                .value(osStats.getCpu().getLoadAverage()[2],"loadavg", "15m");

        writer.addGauge("es_memory")
                .withHelp("Elasticsearch memory stats")
                .value(osStats.getMem().getFree().getBytes(), "memtype", "free")
                .value(osStats.getMem().getUsed().getBytes(), "memtype", "used")
                .value(osStats.getMem().getTotal().getBytes(), "memtype", "total");

        writer.addGauge("es_memory_free_percentage")
                .withHelp("Elasticsearch memory free percentage")
                .value(osStats.getMem().getFreePercent());

        writer.addGauge("es_swap")
                .withHelp("Elasticsearch swap stats")
                .value(osStats.getSwap().getFree().getBytes(), "memtype", "free")
                .value(osStats.getSwap().getUsed().getBytes(), "memtype", "used")
                .value(osStats.getSwap().getTotal().getBytes(), "memtype", "total");

        return writer;
    }
} public class PendingTasksMetricsGenerator extends MetricsGenerator<PendingClusterTasksResponse> {
    private static final Logger logger = Loggers.getLogger(PendingTasksMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer,
                                                  PendingClusterTasksResponse pendingClusterTasksResponse) {
        List<PendingClusterTask> pendingTasks = pendingClusterTasksResponse.getPendingTasks();

        logger.debug("Generating output for PendingTasks stats: {}", pendingTasks);

        writer.addGauge("es_tasks_count")
                .withHelp("Number of background tasks running")
                .value(pendingTasks.size());

        SingleValueWriter esTasksTimeInQueueSeconds = writer
                .addGauge("es_tasks_time_in_queue_millis")
                .withHelp("How long this task is in queue (in seconds)");

        for (PendingClusterTask pendingTask : pendingTasks) {
            esTasksTimeInQueueSeconds.value(pendingTask.getTimeInQueueInMillis(),
                    "id", pendingTask.getSource().toString(),
                    "priority", pendingTask.getPriority().name());
        }

        return writer;
    }
} public class ProcessMetricsGenerator extends MetricsGenerator<ProcessStats> {
    private static final Logger logger = Loggers.getLogger(OsMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, ProcessStats processStats) {
        logger.debug("Generating output for Process stats: {}", processStats);

        writer.addGauge("process_open_fds")
                .withHelp("Number of opened FD handles")
                .value(processStats.getOpenFileDescriptors());

        writer.addGauge("process_max_fds")
                .withHelp("Number of max FD handles available")
                .value(processStats.getOpenFileDescriptors());

        writer.addGauge("process_cpu_seconds_total")
                .withHelp("Total user and system CPU time spent in seconds.")
        .value(processStats.getCpu().getTotal().getSeconds());

        writer.addGauge("process_start_time_seconds")
                .withHelp("Start time of the process since unix epoch in seconds.")
                .value(processStats.getTimestamp());

        writer.addGauge("process_virtual_memory_bytes")
                .withHelp("Virtual memory size in bytes.")
                .value(processStats.getMem().getTotalVirtual().getBytes());

        return writer;
    }
} public class StriptsGenerator extends MetricsGenerator<ScriptStats> {
    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, ScriptStats scriptsStats) {
        writer.addGauge("es_scripts_compilations")
                .withHelp("Number of compilations")
                .value(scriptsStats.getCompilations());

        writer.addGauge("es_scripts_cache_evictions")
                .withHelp("Number of scripts cache evictions")
                .value(scriptsStats.getCacheEvictions());

        writer.addGauge("es_scripts_compilations_limits")
                .withHelp("Number of compilations timeout reached")
                .value(scriptsStats.getCompilationLimitTriggered());
        return writer;
    }
} public class ThreadPoolMetricsGenerator extends MetricsGenerator<ThreadPoolStats> {
    private static final Logger logger = Loggers.getLogger(ThreadPoolMetricsGenerator.class, "init");
    private static final String LABEL_NAME = "threadpool";

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, ThreadPoolStats threadPoolStats) {
        logger.debug("Generating output for ThreadPool stats: {}", threadPoolStats);

        SingleValueWriter threads = writer.addGauge("es_threadpool_threads")
                .withHelp("Number of configured threads in the threadpool");
        SingleValueWriter queue = writer.addGauge("es_threadpool_queue")
                .withHelp("Size of queue configured in the threadpool");
        SingleValueWriter active = writer.addGauge("es_threadpool_active")
                .withHelp("Active threads in the threadpool");
        SingleValueWriter largest = writer.addGauge("es_threadpool_largest")
                .withHelp("Largest number of threads in the threadpool");

        SingleValueWriter completed = writer.addCounter("es_threadpool_completed")
                .withHelp("Number of completed tasks in the threadpool");
        SingleValueWriter rejected = writer.addGauge("es_threadpool_rejected")
                .withHelp("Number of rejected tasks in the threadpool");



        for (Stats threadPoolStat : threadPoolStats) {
            String name = threadPoolStat.getName();

            threads.value(threadPoolStat.getThreads(), LABEL_NAME, name);
            queue.value(threadPoolStat.getQueue(), LABEL_NAME, name);
            active.value(threadPoolStat.getActive(), LABEL_NAME, name);
            largest.value(threadPoolStat.getLargest(), LABEL_NAME, name);

            completed.value(threadPoolStat.getCompleted(), LABEL_NAME, name);
            rejected.value(threadPoolStat.getRejected(), LABEL_NAME, name);
        }
        return writer;
    }
} public class TransportMetricsGenerator extends MetricsGenerator<TransportStats>{
    private static final Logger logger = Loggers.getLogger(OsMetricsGenerator.class, "init");

    @Override
    public PrometheusFormatWriter generateMetrics(PrometheusFormatWriter writer, TransportStats transportStats) {
        logger.debug("Generating output for transport stats: {}", transportStats);

        writer.addGauge("es_transport_server_connections")
                .withHelp("Number of opened server connections")
                .value(transportStats.getServerOpen());

        writer.addGauge("es_transport_rx_packets_count")
                .withHelp("Number of receieved packets")
                .value(transportStats.rxCount());
        writer.addGauge("es_transport_rx_bytes_count")
                .withHelp("Number of receieved packets")
                .value(transportStats.rxSize().getBytes());
        writer.addGauge("es_transport_tx_packets_count")
                .withHelp("Number of send packets")
                .value(transportStats.txCount());
        writer.addGauge("es_transport_tx_bytes_count")
                .withHelp("Total size of send packets")
                .value(transportStats.txSize().getBytes());

        return writer;
    }
} public class PrometheusFormatWriter {
    private final StringWriter writer = new StringWriter();
    private final Map<String, String> globalLabels;

    public PrometheusFormatWriter(Map<String, String> globalLabels) {
        this.globalLabels = globalLabels;
    }

    public SingleMetricsDefinitionBuilder addGauge(String name) {
        return new SingleMetricsDefinitionBuilder(MetricType.GAUGE, writer, name, globalLabels);
    }

    public SingleMetricsDefinitionBuilder addCounter(String name) {
        return new SingleMetricsDefinitionBuilder(MetricType.COUNTER, writer, name, globalLabels);
    }

    public SummaryMetricsDefinitionBuilder addSummary(String name) {
        return new SummaryMetricsDefinitionBuilder(writer, name, globalLabels);
    }

    public String toString() {
        return writer.toString();
    }
} public static CompletableFuture<ClusterHealthResponse> getClusterHealth(final Client client) {
    ActionListenerAdapter<ClusterHealthResponse> result = new ActionListenerAdapter<>();
    ClusterHealthRequest clusterHealthRequest = new ClusterHealthRequest();
    client.admin().cluster().health(clusterHealthRequest, result.asActionListener());
    return result;
} public static CompletableFuture<ClusterState> getClusterState(final Client client) {
    ActionListenerAdapter<ClusterStateResponse> result = new ActionListenerAdapter<>();
    ClusterStateRequest request = new ClusterStateRequest().routingTable(false).nodes(false);

    client.admin().cluster().state(request, result.asActionListener());
    return result.thenApply(ClusterStateResponse::getState);
} public static CompletableFuture<NodeStats> getNodeStats(final Client client) {
    ActionListenerAdapter<NodesStatsResponse> result = new ActionListenerAdapter<>();

    NodesStatsRequest nodesStatsRequest = new NodesStatsRequest("_local").all();
    client.admin().cluster().nodesStats(nodesStatsRequest, result.asActionListener());
    return result.thenApply((response)-> response.getNodes().get(0));
} public static CompletableFuture<NodeUsage> getNodesUsage(final Client client) {
    ActionListenerAdapter<NodesUsageResponse> result = new ActionListenerAdapter<>();
    NodesUsageRequest request = new NodesUsageRequest("_local").all();

    client.admin().cluster().nodesUsage(request, result.asActionListener());

    return result.thenApply((response) -> response.getNodes().get(0));
} public static CompletableFuture<PendingClusterTasksResponse> getPendingTasks(final Client client) {
    ActionListenerAdapter<PendingClusterTasksResponse> result = new ActionListenerAdapter<>();
    PendingClusterTasksRequest request = new PendingClusterTasksRequest();

    client.admin().cluster().pendingClusterTasks(request, result.asActionListener());
    return result;
}
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.CircuitBreakerMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.ClusterHealthMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.ClusterStateMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.FsMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.HttpMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.IndicesMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.IngestMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.JvmMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.NodeUsageGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.OsMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.PendingTasksMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.ProcessMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.StriptsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.ThreadPoolMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.generators.TransportMetricsGenerator;
import pl.suchenia.elasticsearchPrometheusMetrics.writer.PrometheusFormatWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static pl.suchenia.elasticsearchPrometheusMetrics.async.AsyncRequests.getClusterHealth;
import static pl.suchenia.elasticsearchPrometheusMetrics.async.AsyncRequests.getClusterState;
import static pl.suchenia.elasticsearchPrometheusMetrics.async.AsyncRequests.getNodeStats;
import static pl.suchenia.elasticsearchPrometheusMetrics.async.AsyncRequests.getNodesUsage;
import static pl.suchenia.elasticsearchPrometheusMetrics.async.AsyncRequests.getPendingTasks;
package pl.suchenia.elasticsearchPrometheusMetrics;




public class PrometheusExporterPlugin extends Plugin implements ActionPlugin {
    private static final Logger logger = Loggers.getLogger(PrometheusExporterPlugin.class, "init");

    private final JvmMetricsGenerator jvmMetricsGenerator = new JvmMetricsGenerator();
    private final IndicesMetricsGenerator indicesMetricsGenerator = new IndicesMetricsGenerator();
    private final ClusterHealthMetricsGenerator clusterHealthMetricsGenerator = new ClusterHealthMetricsGenerator();
    private final OsMetricsGenerator osMetricsGenerator = new OsMetricsGenerator();
    private final ClusterStateMetricsGenerator clusterStateMetricsGenerator = new ClusterStateMetricsGenerator();
    private final TransportMetricsGenerator transportMetricsGenerator = new TransportMetricsGenerator();
    private final IngestMetricsGenerator ingestMetricsGenerator = new IngestMetricsGenerator();
    private final ProcessMetricsGenerator processMetricsGenerator = new ProcessMetricsGenerator();
    private final PendingTasksMetricsGenerator pendingTasksMetricsGenerator = new PendingTasksMetricsGenerator();
    private final CircuitBreakerMetricsGenerator circuitBreakerMetricsGenerator = new CircuitBreakerMetricsGenerator();
    private final FsMetricsGenerator fsMetricsGenerator = new FsMetricsGenerator();
    private final ThreadPoolMetricsGenerator threadPoolMetricsGenerator = new ThreadPoolMetricsGenerator();
    private final NodeUsageGenerator nodeUsageGenerator = new NodeUsageGenerator();
    private final HttpMetricsGenerator httpMetricsGenerator = new HttpMetricsGenerator();
    private final StriptsGenerator scriptsGenerator = new StriptsGenerator();

    private final Map<String, StringBufferedRestHandler> handlers = new HashMap<>();

    public PrometheusExporterPlugin() {
        logger.info("Initializing prometheus reporting plugin");
        handlers.put("/_prometheus/node", (channel, client) -> {
            logger.debug("Generating JVM stats in prometheus format");

            return createWriter(client).thenCombine(getNodeStats(client), this::generateNodeMetrics);