package org.apache.cassandra.utils;
/*
 * 
 * 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.
 * 
 */


import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.util.StringTokenizer;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.cassandra.concurrent.DebuggableScheduledThreadPoolExecutor;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.AtomicDouble;

public class BackgroundActivityMonitor
{
    private static final Logger logger = LoggerFactory.getLogger(BackgroundActivityMonitor.class);

    public static final int USER_INDEX = 0;
    public static final int NICE_INDEX = 1;
    public static final int SYS_INDEX = 2;
    public static final int IDLE_INDEX = 3;
    public static final int IOWAIT_INDEX = 4;
    public static final int IRQ_INDEX = 5;
    public static final int SOFTIRQ_INDEX = 6;

    private static final int NUM_CPUS = Runtime.getRuntime().availableProcessors();
    private static final String PROC_STAT_PATH = "/proc/stat";

    private final AtomicDouble compaction_severity = new AtomicDouble();
    private final AtomicDouble manual_severity = new AtomicDouble();
    private final ScheduledExecutorService reportThread = new DebuggableScheduledThreadPoolExecutor("Background_Reporter");

    private RandomAccessFile statsFile;
    private long[] lastReading;

    public BackgroundActivityMonitor()
    {
        try
        {
            statsFile = new RandomAccessFile(PROC_STAT_PATH, "r");
            lastReading = readAndCompute();
        }
        catch (IOException ex)
        {
            if (FBUtilities.isUnix())
                logger.warn("Couldn't open /proc/stats");
            statsFile = null;
        }
        reportThread.scheduleAtFixedRate(new BackgroundActivityReporter(), 1, 1, TimeUnit.SECONDS);
    }

    private long[] readAndCompute() throws IOException
    {
        statsFile.seek(0);
        StringTokenizer tokenizer = new StringTokenizer(statsFile.readLine());
        String name = tokenizer.nextToken();
        assert name.equalsIgnoreCase("cpu");
        long[] returned = new long[tokenizer.countTokens()];
        for (int i = 0; i < returned.length; i++)
            returned[i] = Long.parseLong(tokenizer.nextToken());
        return returned;
    }

    private float compareAtIndex(long[] reading1, long[] reading2, int index)
    {
        long total1 = 0, total2 = 0;
        for (int i = 0; i <= SOFTIRQ_INDEX; i++)
        {
            total1 += reading1[i];
            total2 += reading2[i];
        }
        float totalDiff = total2 - total1;

        long intrested1 = reading1[index], intrested2 = reading2[index];
        float diff = intrested2 - intrested1;
        if (diff == 0)
            return 0f;
        return (diff / totalDiff) * 100; // yes it is hard coded to 100 [update
                                         // unit?]
    }

    public void incrCompactionSeverity(double sev)
    {
        compaction_severity.addAndGet(sev);
    }

    public void incrManualSeverity(double sev)
    {
        manual_severity.addAndGet(sev);
    }

    public double getIOWait() throws IOException
    {
        if (statsFile == null)
            return -1d;
        long[] newComp = readAndCompute();
        double value = compareAtIndex(lastReading, newComp, IOWAIT_INDEX);
        lastReading = newComp;
        return value;
    }

    public double getNormalizedLoadAvg()
    {
        double avg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
        return avg / NUM_CPUS;
    }

    public double getSeverity(InetAddress endpoint)
    {
        VersionedValue event;
        EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
        if (state != null && (event = state.getApplicationState(ApplicationState.SEVERITY)) != null)
            return Double.parseDouble(event.value);
        return 0.0;
    }

    public class BackgroundActivityReporter implements Runnable
    {
        public void run()
        {
            double report = -1;
            try
            {
                report = getIOWait();
            }
            catch (IOException e)
            {
                // ignore;
                if (FBUtilities.isUnix())
                    logger.warn("Couldn't read /proc/stats");
            }
            if (report == -1d)
                report = compaction_severity.get();

            if (!Gossiper.instance.isEnabled())
                return;
            report += manual_severity.get(); // add manual severity setting.
            VersionedValue updated = StorageService.instance.valueFactory.severity(report);
            Gossiper.instance.addLocalApplicationState(ApplicationState.SEVERITY, updated);
        }
    }
}
