/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.cassandra.metrics;

import java.util.Set;
import java.util.function.ToLongFunction;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.metrics.CassandraMetricsRegistry.MetricName;
import org.apache.cassandra.metrics.TableMetrics.ReleasableMetric;

import com.google.common.collect.Sets;

import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;

/**
 * Metrics for {@link ColumnFamilyStore}.
 */
public class KeyspaceMetrics
{
    /** Total amount of live data stored in the memtable, excluding any data structure overhead */
    public final Gauge<Long> memtableLiveDataSize;
    /** Total amount of data stored in the memtable that resides on-heap, including column related overhead and partitions overwritten. */
    public final Gauge<Long> memtableOnHeapDataSize;
    /** Total amount of data stored in the memtable that resides off-heap, including column related overhead and partitions overwritten. */
    public final Gauge<Long> memtableOffHeapDataSize;
    /** Total amount of live data stored in the memtables (2i and pending flush memtables included) that resides off-heap, excluding any data structure overhead */
    public final Gauge<Long> allMemtablesLiveDataSize;
    /** Total amount of data stored in the memtables (2i and pending flush memtables included) that resides on-heap. */
    public final Gauge<Long> allMemtablesOnHeapDataSize;
    /** Total amount of data stored in the memtables (2i and pending flush memtables included) that resides off-heap. */
    public final Gauge<Long> allMemtablesOffHeapDataSize;
    /** Total number of columns present in the memtable. */
    public final Gauge<Long> memtableColumnsCount;
    /** Number of times flush has resulted in the memtable being switched out. */
    public final Gauge<Long> memtableSwitchCount;
    /** Estimated number of tasks pending for this column family */
    public final Gauge<Long> pendingFlushes;
    /** Estimate of number of pending compactios for this CF */
    public final Gauge<Long> pendingCompactions;
    /** Disk space used by SSTables belonging to this CF */
    public final Gauge<Long> liveDiskSpaceUsed;
    /** Total disk space used by SSTables belonging to this CF, including obsolete ones waiting to be GC'd */
    public final Gauge<Long> totalDiskSpaceUsed;
    /** Disk space used by bloom filter */
    public final Gauge<Long> bloomFilterDiskSpaceUsed;
    /** Off heap memory used by bloom filter */
    public final Gauge<Long> bloomFilterOffHeapMemoryUsed;
    /** Off heap memory used by index summary */
    public final Gauge<Long> indexSummaryOffHeapMemoryUsed;
    /** Off heap memory used by compression meta data*/
    public final Gauge<Long> compressionMetadataOffHeapMemoryUsed;
    /** (Local) read metrics */
    public final LatencyMetrics readLatency;
    /** (Local) range slice metrics */
    public final LatencyMetrics rangeLatency;
    /** (Local) write metrics */
    public final LatencyMetrics writeLatency;
    /** Histogram of the number of sstable data files accessed per read */
    public final Histogram sstablesPerReadHistogram;
    /** Tombstones scanned in queries on this Keyspace */
    public final Histogram tombstoneScannedHistogram;
    /** Live cells scanned in queries on this Keyspace */
    public final Histogram liveScannedHistogram;
    /** Column update time delta on this Keyspace */
    public final Histogram colUpdateTimeDeltaHistogram;
    /** time taken acquiring the partition lock for materialized view updates on this keyspace */
    public final Timer viewLockAcquireTime;
    /** time taken during the local read of a materialized view update */
    public final Timer viewReadTime;
    /** CAS Prepare metric */
    public final LatencyMetrics casPrepare;
    /** CAS Propose metrics */
    public final LatencyMetrics casPropose;
    /** CAS Commit metrics */
    public final LatencyMetrics casCommit;
    /** Writes failed ideal consistency **/
    public final Counter writeFailedIdealCL;
    /** Ideal CL write latency metrics */
    public final LatencyMetrics idealCLWriteLatency;
    /** Speculative retries **/
    public final Counter speculativeRetries;
    /** Speculative retry occured but still timed out **/
    public final Counter speculativeFailedRetries;
    /** Needed to speculate, but didn't have enough replicas **/
    public final Counter speculativeInsufficientReplicas;
    /** Needed to write to a transient replica to satisfy quorum **/
    public final Counter additionalWrites;
    /** Number of started repairs as coordinator on this keyspace */
    public final Counter repairsStarted;
    /** Number of completed repairs as coordinator on this keyspace */
    public final Counter repairsCompleted;
    /** total time spent as a repair coordinator */
    public final Timer repairTime;
    /** total time spent preparing for repair */
    public final Timer repairPrepareTime;
    /** Time spent anticompacting */
    public final Timer anticompactionTime;
    /** total time spent creating merkle trees */
    public final Timer validationTime;
    /** total time spent syncing data after repair */
    public final Timer repairSyncTime;
    /** histogram over the number of bytes we have validated */
    public final Histogram bytesValidated;
    /** histogram over the number of partitions we have validated */
    public final Histogram partitionsValidated;
    /** Lifetime count of reads for keys outside the node's owned token ranges for this keyspace **/
    public final Counter outOfRangeTokenReads;
    /** Lifetime count of writes for keys outside the node's owned token ranges for this keyspace **/
    public final Counter outOfRangeTokenWrites;
    /** Lifetime count of paxos requests for keys outside the node's owned token ranges for this keyspace **/
    public final Counter outOfRangeTokenPaxosRequests;

    /*
     * Metrics for inconsistencies detected between repaired data sets across replicas. These
     * are tracked on the coordinator.
     */

    /**
     * Incremented where an inconsistency is detected and there are no pending repair sessions affecting
     * the data being read, indicating a genuine mismatch between replicas' repaired data sets.
     */
    public final Meter confirmedRepairedInconsistencies;
    /**
     * Incremented where an inconsistency is detected, but there are pending & uncommitted repair sessions
     * in play on at least one replica. This may indicate a false positive as the inconsistency could be due to
     * replicas marking the repair session as committed at slightly different times and so some consider it to
     * be part of the repaired set whilst others do not.
     */
    public final Meter unconfirmedRepairedInconsistencies;

    /**
     * Tracks the amount overreading of repaired data replicas perform in order to produce digests
     * at query time. For each query, on a full data read following an initial digest mismatch, the replicas
     * may read extra repaired data, up to the DataLimit of the command, so that the coordinator can compare
     * the repaired data on each replica. These are tracked on each replica.
     */
    public final Histogram repairedDataTrackingOverreadRows;
    public final Timer repairedDataTrackingOverreadTime;

    public final MetricNameFactory factory;
    private Keyspace keyspace;

    /** set containing names of all the metrics stored here, for releasing later */
    private Set<ReleasableMetric> allMetrics = Sets.newHashSet();

    /**
     * Creates metrics for given {@link ColumnFamilyStore}.
     *
     * @param ks Keyspace to measure metrics
     */
    public KeyspaceMetrics(final Keyspace ks)
    {
        factory = new KeyspaceMetricNameFactory(ks);
        keyspace = ks;
        memtableColumnsCount = createKeyspaceGauge("MemtableColumnsCount",
                metric -> metric.memtableColumnsCount.getValue());
        memtableLiveDataSize = createKeyspaceGauge("MemtableLiveDataSize",
                metric -> metric.memtableLiveDataSize.getValue());
        memtableOnHeapDataSize = createKeyspaceGauge("MemtableOnHeapDataSize",
                metric -> metric.memtableOnHeapDataSize.getValue());
        memtableOffHeapDataSize = createKeyspaceGauge("MemtableOffHeapDataSize",
                metric -> metric.memtableOffHeapDataSize.getValue());
        allMemtablesLiveDataSize = createKeyspaceGauge("AllMemtablesLiveDataSize",
                metric -> metric.allMemtablesLiveDataSize.getValue());
        allMemtablesOnHeapDataSize = createKeyspaceGauge("AllMemtablesOnHeapDataSize",
                metric -> metric.allMemtablesOnHeapDataSize.getValue());
        allMemtablesOffHeapDataSize = createKeyspaceGauge("AllMemtablesOffHeapDataSize",
                metric -> metric.allMemtablesOffHeapDataSize.getValue());
        memtableSwitchCount = createKeyspaceGauge("MemtableSwitchCount",
                metric -> metric.memtableSwitchCount.getCount());
        pendingCompactions = createKeyspaceGauge("PendingCompactions", metric -> metric.pendingCompactions.getValue());
        pendingFlushes = createKeyspaceGauge("PendingFlushes", metric -> metric.pendingFlushes.getCount());
        liveDiskSpaceUsed = createKeyspaceGauge("LiveDiskSpaceUsed", metric -> metric.liveDiskSpaceUsed.getCount());
        totalDiskSpaceUsed = createKeyspaceGauge("TotalDiskSpaceUsed", metric -> metric.totalDiskSpaceUsed.getCount());
        bloomFilterDiskSpaceUsed = createKeyspaceGauge("BloomFilterDiskSpaceUsed",
                metric -> metric.bloomFilterDiskSpaceUsed.getValue());
        bloomFilterOffHeapMemoryUsed = createKeyspaceGauge("BloomFilterOffHeapMemoryUsed",
                metric -> metric.bloomFilterOffHeapMemoryUsed.getValue());
        indexSummaryOffHeapMemoryUsed = createKeyspaceGauge("IndexSummaryOffHeapMemoryUsed",
                metric -> metric.indexSummaryOffHeapMemoryUsed.getValue());
        compressionMetadataOffHeapMemoryUsed = createKeyspaceGauge("CompressionMetadataOffHeapMemoryUsed",
                metric -> metric.compressionMetadataOffHeapMemoryUsed.getValue());

        // latency metrics for TableMetrics to update
        readLatency = createLatencyMetrics("Read");
        writeLatency = createLatencyMetrics("Write");
        rangeLatency = createLatencyMetrics("Range");

        // create histograms for TableMetrics to replicate updates to
        sstablesPerReadHistogram = createKeyspaceHistogram("SSTablesPerReadHistogram", true);
        tombstoneScannedHistogram = createKeyspaceHistogram("TombstoneScannedHistogram", false);
        liveScannedHistogram = createKeyspaceHistogram("LiveScannedHistogram", false);
        colUpdateTimeDeltaHistogram = createKeyspaceHistogram("ColUpdateTimeDeltaHistogram", false);
        viewLockAcquireTime = createKeyspaceTimer("ViewLockAcquireTime");
        viewReadTime = createKeyspaceTimer("ViewReadTime");

        casPrepare = createLatencyMetrics("CasPrepare");
        casPropose = createLatencyMetrics("CasPropose");
        casCommit = createLatencyMetrics("CasCommit");
        writeFailedIdealCL = createKeyspaceCounter("WriteFailedIdealCL");
        idealCLWriteLatency = createLatencyMetrics("IdealCLWrite");

        speculativeRetries = createKeyspaceCounter("SpeculativeRetries", metric -> metric.speculativeRetries.getCount());
        speculativeFailedRetries = createKeyspaceCounter("SpeculativeFailedRetries", metric -> metric.speculativeFailedRetries.getCount());
        speculativeInsufficientReplicas = createKeyspaceCounter("SpeculativeInsufficientReplicas", metric -> metric.speculativeInsufficientReplicas.getCount());
        additionalWrites = createKeyspaceCounter("AdditionalWrites", metric -> metric.additionalWrites.getCount());
        repairsStarted = createKeyspaceCounter("RepairJobsStarted", metric -> metric.repairsStarted.getCount());
        repairsCompleted = createKeyspaceCounter("RepairJobsCompleted", metric -> metric.repairsCompleted.getCount());
        repairTime =createKeyspaceTimer("RepairTime");
        repairPrepareTime = createKeyspaceTimer("RepairPrepareTime");
        anticompactionTime = createKeyspaceTimer("AntiCompactionTime");
        validationTime = createKeyspaceTimer("ValidationTime");
        repairSyncTime = createKeyspaceTimer("RepairSyncTime");
        partitionsValidated = createKeyspaceHistogram("PartitionsValidated", false);
        bytesValidated = createKeyspaceHistogram("BytesValidated", false);

        confirmedRepairedInconsistencies = createKeyspaceMeter("RepairedDataInconsistenciesConfirmed");
        unconfirmedRepairedInconsistencies = createKeyspaceMeter("RepairedDataInconsistenciesUnconfirmed");

        repairedDataTrackingOverreadRows = createKeyspaceHistogram("RepairedDataTrackingOverreadRows", false);
        repairedDataTrackingOverreadTime = createKeyspaceTimer("RepairedDataTrackingOverreadTime");

        outOfRangeTokenReads = createKeyspaceCounter("ReadOutOfRangeToken");
        outOfRangeTokenWrites = createKeyspaceCounter("WriteOutOfRangeToken");
        outOfRangeTokenPaxosRequests = createKeyspaceCounter("PaxosOutOfRangeToken");
    }

    /**
     * Release all associated metrics.
     */
    public void release()
    {
        for (ReleasableMetric metric : allMetrics)
        {
            metric.release();
        }
    }

    /**
     * Creates a gauge that will sum the current value of a metric for all column families in this keyspace
     * @param name
     * @param extractor
     * @return Gauge&gt;Long> that computes sum of MetricValue.getValue()
     */
    private Gauge<Long> createKeyspaceGauge(String name, final ToLongFunction<TableMetrics> extractor)
    {
        allMetrics.add(() -> releaseMetric(name));
        return Metrics.register(factory.createMetricName(name), new Gauge<Long>()
        {
            public Long getValue()
            {
                long sum = 0;
                for (ColumnFamilyStore cf : keyspace.getColumnFamilyStores())
                {
                    sum += extractor.applyAsLong(cf.metric);
                }
                return sum;
            }
        });
    }

    /**
     * Creates a counter that will sum the current value of a metric for all column families in this keyspace
     * @param name
     * @param extractor
     * @return Counter that computes sum of MetricValue.getValue()
     */
    private Counter createKeyspaceCounter(String name, final ToLongFunction<TableMetrics> extractor)
    {
        allMetrics.add(() -> releaseMetric(name));
        return Metrics.register(factory.createMetricName(name), new Counter()
        {
            @Override
            public long getCount()
            {
                long sum = 0;
                for (ColumnFamilyStore cf : keyspace.getColumnFamilyStores())
                {
                    sum += extractor.applyAsLong(cf.metric);
                }
                return sum;
            }
        });
    }

    protected Counter createKeyspaceCounter(String name)
    {
        allMetrics.add(() -> releaseMetric(name));
        return Metrics.counter(factory.createMetricName(name));
    }

    protected Histogram createKeyspaceHistogram(String name, boolean considerZeroes)
    {
        allMetrics.add(() -> releaseMetric(name));
        return Metrics.histogram(factory.createMetricName(name), considerZeroes);
    }

    protected Timer createKeyspaceTimer(String name)
    {
        allMetrics.add(() -> releaseMetric(name));
        return Metrics.timer(factory.createMetricName(name));
    }

    protected Meter createKeyspaceMeter(String name)
    {
        allMetrics.add(() -> releaseMetric(name));
        return Metrics.meter(factory.createMetricName(name));
    }

    private LatencyMetrics createLatencyMetrics(String name)
    {
        LatencyMetrics metric = new LatencyMetrics(factory, name);
        allMetrics.add(() -> metric.release());
        return metric;
    }

    private void releaseMetric(String name)
    {
        Metrics.remove(factory.createMetricName(name));
    }

    static class KeyspaceMetricNameFactory implements MetricNameFactory
    {
        private final String keyspaceName;

        KeyspaceMetricNameFactory(Keyspace ks)
        {
            this.keyspaceName = ks.getName();
        }

        @Override
        public MetricName createMetricName(String metricName)
        {
            String groupName = TableMetrics.class.getPackage().getName();

            StringBuilder mbeanName = new StringBuilder();
            mbeanName.append(groupName).append(":");
            mbeanName.append("type=Keyspace");
            mbeanName.append(",keyspace=").append(keyspaceName);
            mbeanName.append(",name=").append(metricName);

            return new MetricName(groupName, "keyspace", metricName, keyspaceName, mbeanName.toString());
        }
    }
}
