/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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.
 */

#ifndef _STORAGED_DISKSTATS_H_
#define _STORAGED_DISKSTATS_H_

#include <stdint.h>

#include <android/hardware/health/2.0/IHealth.h>

// number of attributes diskstats has
#define DISK_STATS_SIZE ( 11 )

#define MMC_DISK_STATS_PATH "/sys/block/mmcblk0/stat"
#define SDA_DISK_STATS_PATH "/sys/block/sda/stat"

struct disk_stats {
    /* It will be extremely unlikely for any of the following entries to overflow.
     * For read_bytes(which will be greater than any of the following entries), it
     * will take 27 years to overflow uint64_t at the reading rate of 20GB/s, which
     * is the peak memory transfer rate for current memory.
     * The diskstats entries (first 11) need to be at top in this structure _after_
     * compiler's optimization.
     */
    uint64_t read_ios;       // number of read I/Os processed
    uint64_t read_merges;    // number of read I/Os merged with in-queue I/Os
    uint64_t read_sectors;   // number of sectors read
    uint64_t read_ticks;     // total wait time for read requests
    uint64_t write_ios;      // number of write I/Os processed
    uint64_t write_merges;   // number of write I/Os merged with in-queue I/Os
    uint64_t write_sectors;  // number of sectors written
    uint64_t write_ticks;    // total wait time for write requests
    uint64_t io_in_flight;   // number of I/Os currently in flight
    uint64_t io_ticks;       // total time this block device has been active
    uint64_t io_in_queue;    // total wait time for all requests

    uint64_t start_time;     // monotonic time accounting starts
    uint64_t end_time;       // monotonic time accounting ends
    uint32_t counter;        // private counter for accumulate calculations
    double   io_avg;         // average io_in_flight for accumulate calculations

    bool is_zero() {
        return read_ios == 0 && write_ios == 0 &&
               io_in_flight == 0 && io_ticks == 0 && io_in_queue == 0;
    }

    friend disk_stats operator- (disk_stats curr, const disk_stats& prev) {
        curr.read_ios -= prev.read_ios;
        curr.read_merges -= prev.read_merges;
        curr.read_sectors -= prev.read_sectors;
        curr.read_ticks -= prev.read_ticks;
        curr.write_ios -= prev.write_ios;
        curr.write_merges -= prev.write_merges;
        curr.write_sectors -= prev.write_sectors;
        curr.write_ticks -= prev.write_ticks;
        /* skips io_in_flight, use current value */
        curr.io_ticks -= prev.io_ticks;
        curr.io_in_queue -= prev.io_in_queue;
        return curr;
    }

    friend bool operator== (const disk_stats& a, const disk_stats& b) {
        return a.read_ios == b.read_ios &&
               a.read_merges == b.read_merges &&
               a.read_sectors == b.read_sectors &&
               a.read_ticks == b.read_ticks &&
               a.write_ios == b.write_ios &&
               a.write_merges == b.write_merges &&
               a.write_sectors == b.write_sectors &&
               a.write_ticks == b.write_ticks &&
               /* skips io_in_flight */
               a.io_ticks == b.io_ticks &&
               a.io_in_queue == b.io_in_queue;
    }

    disk_stats& operator+= (const disk_stats& stats) {
        read_ios += stats.read_ios;
        read_merges += stats.read_merges;
        read_sectors += stats.read_sectors;
        read_ticks += stats.read_ticks;
        write_ios += stats.write_ios;
        write_merges += stats.write_merges;
        write_sectors += stats.write_sectors;
        write_ticks += stats.write_ticks;
        /* skips io_in_flight, use current value */
        io_ticks += stats.io_ticks;
        io_in_queue += stats.io_in_queue;
        return *this;
    }
};

struct disk_perf {
    uint32_t read_perf;         // read speed (kbytes/s)
    uint32_t read_ios;          // read I/Os per second
    uint32_t write_perf;        // write speed (kbytes/s)
    uint32_t write_ios;         // write I/Os per second
    uint32_t queue;             // I/Os in queue
    bool is_zero() {
        return read_perf == 0 && read_ios == 0 &&
               write_perf == 0 && write_ios == 0 && queue == 0;
    }
};

class stream_stats {
private:
    double mSum;
    double mSquareSum;
    uint32_t mCnt;
public:
    stream_stats() : mSum(0), mSquareSum(0), mCnt(0) {};
    ~stream_stats() {};
    double get_mean() {
        return mSum / mCnt;
    }
    double get_std() {
        return sqrt(mSquareSum / mCnt - mSum * mSum / (mCnt * mCnt));
    }
    void add(uint32_t num) {
        mSum += (double)num;
        mSquareSum += (double)num * (double)num;
        mCnt++;
    }
    void evict(uint32_t num) {
        if (mSum < num || mSquareSum < (double)num * (double)num) return;
        mSum -= (double)num;
        mSquareSum -= (double)num * (double)num;
        mCnt--;
    }
};

class disk_stats_monitor {
private:
    FRIEND_TEST(storaged_test, disk_stats_monitor);
    const char* const DISK_STATS_PATH;
    struct disk_stats mPrevious;
    struct disk_stats mAccumulate;      /* reset after stall */
    struct disk_stats mAccumulate_pub;  /* reset after publish */
    bool mStall;
    std::queue<struct disk_perf> mBuffer;
    struct {
        stream_stats read_perf;           // read speed (bytes/s)
        stream_stats read_ios;            // read I/Os per second
        stream_stats write_perf;          // write speed (bytes/s)
        stream_stats write_ios;           // write I/O per second
        stream_stats queue;               // I/Os in queue
    } mStats;
    bool mValid;
    const uint32_t mWindow;
    const double mSigma;
    struct disk_perf mMean;
    struct disk_perf mStd;
    android::sp<android::hardware::health::V2_0::IHealth> mHealth;

    void update_mean();
    void update_std();
    void add(struct disk_perf* perf);
    void evict(struct disk_perf* perf);
    bool detect(struct disk_perf* perf);

    void update(struct disk_stats* stats);

public:
  disk_stats_monitor(const android::sp<android::hardware::health::V2_0::IHealth>& healthService,
                     uint32_t window_size = 5, double sigma = 1.0)
      : DISK_STATS_PATH(
            healthService != nullptr
                ? nullptr
                : (access(MMC_DISK_STATS_PATH, R_OK) == 0
                       ? MMC_DISK_STATS_PATH
                       : (access(SDA_DISK_STATS_PATH, R_OK) == 0 ? SDA_DISK_STATS_PATH : nullptr))),
        mPrevious(),
        mAccumulate(),
        mAccumulate_pub(),
        mStall(false),
        mValid(false),
        mWindow(window_size),
        mSigma(sigma),
        mMean(),
        mStd(),
        mHealth(healthService) {}
  bool enabled() { return mHealth != nullptr || DISK_STATS_PATH != nullptr; }
  void update(void);
  void publish(void);
};

#endif /* _STORAGED_DISKSTATS_H_ */
