/*
 * Copyright (C) 2015 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 METRICS_METRICS_DAEMON_H_
#define METRICS_METRICS_DAEMON_H_

#include <stdint.h>

#include <map>
#include <string>
#include <vector>

#include <base/files/file_path.h>
#include <base/memory/scoped_ptr.h>
#include <base/time/time.h>
#include <chromeos/daemons/dbus_daemon.h>
#include <gtest/gtest_prod.h>  // for FRIEND_TEST

#include "metrics/metrics_library.h"
#include "persistent_integer.h"
#include "uploader/upload_service.h"

using chromeos_metrics::PersistentInteger;

class MetricsDaemon : public chromeos::DBusDaemon {
 public:
  MetricsDaemon();
  ~MetricsDaemon();

  // Initializes metrics class variables.
  void Init(bool testing,
            bool uploader_active,
            bool dbus_enabled,
            MetricsLibraryInterface* metrics_lib,
            const std::string& vmstats_path,
            const std::string& cpuinfo_max_freq_path,
            const std::string& scaling_max_freq_path,
            const base::TimeDelta& upload_interval,
            const std::string& server,
            const std::string& metrics_file,
            const std::string& config_root);

  // Initializes DBus and MessageLoop variables before running the MessageLoop.
  int OnInit() override;

  // Clean up data set up in OnInit before shutting down message loop.
  void OnShutdown(int* return_code) override;

  // Does all the work.
  int Run() override;

  // Triggers an upload event and exit. (Used to test UploadService)
  void RunUploaderTest();

 protected:
  // Used also by the unit tests.
  static const char kComprDataSizeName[];
  static const char kOrigDataSizeName[];
  static const char kZeroPagesName[];

 private:
  friend class MetricsDaemonTest;
  FRIEND_TEST(MetricsDaemonTest, CheckSystemCrash);
  FRIEND_TEST(MetricsDaemonTest, ComputeEpochNoCurrent);
  FRIEND_TEST(MetricsDaemonTest, ComputeEpochNoLast);
  FRIEND_TEST(MetricsDaemonTest, GetHistogramPath);
  FRIEND_TEST(MetricsDaemonTest, IsNewEpoch);
  FRIEND_TEST(MetricsDaemonTest, MessageFilter);
  FRIEND_TEST(MetricsDaemonTest, ParseVmStats);
  FRIEND_TEST(MetricsDaemonTest, ProcessKernelCrash);
  FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo);
  FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo2);
  FRIEND_TEST(MetricsDaemonTest, ProcessUncleanShutdown);
  FRIEND_TEST(MetricsDaemonTest, ProcessUserCrash);
  FRIEND_TEST(MetricsDaemonTest, ReportCrashesDailyFrequency);
  FRIEND_TEST(MetricsDaemonTest, ReadFreqToInt);
  FRIEND_TEST(MetricsDaemonTest, ReportDiskStats);
  FRIEND_TEST(MetricsDaemonTest, ReportKernelCrashInterval);
  FRIEND_TEST(MetricsDaemonTest, ReportUncleanShutdownInterval);
  FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval);
  FRIEND_TEST(MetricsDaemonTest, SendSample);
  FRIEND_TEST(MetricsDaemonTest, SendCpuThrottleMetrics);
  FRIEND_TEST(MetricsDaemonTest, SendZramMetrics);

  // State for disk stats collector callback.
  enum StatsState {
    kStatsShort,    // short wait before short interval collection
    kStatsLong,     // final wait before new collection
  };

  // Data record for aggregating daily usage.
  class UseRecord {
   public:
    UseRecord() : day_(0), seconds_(0) {}
    int day_;
    int seconds_;
  };

  // Type of scale to use for meminfo histograms.  For most of them we use
  // percent of total RAM, but for some we use absolute numbers, usually in
  // megabytes, on a log scale from 0 to 4000, and 0 to 8000 for compressed
  // swap (since it can be larger than total RAM).
  enum MeminfoOp {
    kMeminfoOp_HistPercent = 0,
    kMeminfoOp_HistLog,
    kMeminfoOp_SwapTotal,
    kMeminfoOp_SwapFree,
  };

  // Record for retrieving and reporting values from /proc/meminfo.
  struct MeminfoRecord {
    const char* name;        // print name
    const char* match;       // string to match in output of /proc/meminfo
    MeminfoOp op;            // histogram scale selector, or other operator
    int value;               // value from /proc/meminfo
  };

  // Record for retrieving and reporting values from /proc/vmstat
  struct VmstatRecord {
    uint64_t page_faults_;    // major faults
    uint64_t swap_in_;        // pages swapped in
    uint64_t swap_out_;       // pages swapped out
  };

  // Metric parameters.
  static const char kMetricReadSectorsLongName[];
  static const char kMetricReadSectorsShortName[];
  static const char kMetricWriteSectorsLongName[];
  static const char kMetricWriteSectorsShortName[];
  static const char kMetricPageFaultsShortName[];
  static const char kMetricPageFaultsLongName[];
  static const char kMetricSwapInLongName[];
  static const char kMetricSwapInShortName[];
  static const char kMetricSwapOutLongName[];
  static const char kMetricSwapOutShortName[];
  static const char kMetricScaledCpuFrequencyName[];
  static const int kMetricStatsShortInterval;
  static const int kMetricStatsLongInterval;
  static const int kMetricMeminfoInterval;
  static const int kMetricSectorsIOMax;
  static const int kMetricSectorsBuckets;
  static const int kMetricPageFaultsMax;
  static const int kMetricPageFaultsBuckets;
  static const char kMetricsDiskStatsPath[];
  static const char kMetricsVmStatsPath[];
  static const char kMetricsProcStatFileName[];
  static const int kMetricsProcStatFirstLineItemsCount;

  // Returns the active time since boot (uptime minus sleep time) in seconds.
  double GetActiveTime();

  // D-Bus filter callback.
  static DBusHandlerResult MessageFilter(DBusConnection* connection,
                                         DBusMessage* message,
                                         void* user_data);

  // Updates the daily usage file, if necessary, by adding |seconds|
  // of active use to the |day| since Epoch. If there's usage data for
  // day in the past in the usage file, that data is sent to UMA and
  // removed from the file. If there's already usage data for |day| in
  // the usage file, the |seconds| are accumulated.
  void LogDailyUseRecord(int day, int seconds);

  // Updates the active use time and logs time between user-space
  // process crashes.
  void ProcessUserCrash();

  // Updates the active use time and logs time between kernel crashes.
  void ProcessKernelCrash();

  // Updates the active use time and logs time between unclean shutdowns.
  void ProcessUncleanShutdown();

  // Checks if a kernel crash has been detected and returns true if
  // so.  The method assumes that a kernel crash has happened if
  // |crash_file| exists.  It removes the file immediately if it
  // exists, so it must not be called more than once.
  bool CheckSystemCrash(const std::string& crash_file);

  // Sends a regular (exponential) histogram sample to Chrome for
  // transport to UMA. See MetricsLibrary::SendToUMA in
  // metrics_library.h for a description of the arguments.
  void SendSample(const std::string& name, int sample,
                  int min, int max, int nbuckets);

  // Sends a linear histogram sample to Chrome for transport to UMA. See
  // MetricsLibrary::SendToUMA in metrics_library.h for a description of the
  // arguments.
  void SendLinearSample(const std::string& name, int sample,
                        int max, int nbuckets);

  // Sends various cumulative kernel crash-related stats, for instance the
  // total number of kernel crashes since the last version update.
  void SendKernelCrashesCumulativeCountStats();

  // Returns the total (system-wide) CPU usage between the time of the most
  // recent call to this function and now.
  base::TimeDelta GetIncrementalCpuUse();

  // Sends a sample representing the number of seconds of active use
  // for a 24-hour period.
  void SendDailyUseSample(const scoped_ptr<PersistentInteger>& use);

  // Sends a sample representing a time interval between two crashes of the
  // same type.
  void SendCrashIntervalSample(const scoped_ptr<PersistentInteger>& interval);

  // Sends a sample representing a frequency of crashes of some type.
  void SendCrashFrequencySample(const scoped_ptr<PersistentInteger>& frequency);

  // Initializes vm and disk stats reporting.
  void StatsReporterInit();

  // Schedules a callback for the next vm and disk stats collection.
  void ScheduleStatsCallback(int wait);

  // Reads cumulative disk statistics from sysfs.  Returns true for success.
  bool DiskStatsReadStats(uint64_t* read_sectors, uint64_t* write_sectors);

  // Reads cumulative vm statistics from procfs.  Returns true for success.
  bool VmStatsReadStats(struct VmstatRecord* stats);

  // Parse cumulative vm statistics from a C string.  Returns true for success.
  bool VmStatsParseStats(const char* stats, struct VmstatRecord* record);

  // Reports disk and vm statistics.
  void StatsCallback();

  // Schedules meminfo collection callback.
  void ScheduleMeminfoCallback(int wait);

  // Reports memory statistics.  Reschedules callback on success.
  void MeminfoCallback(base::TimeDelta wait);

  // Parses content of /proc/meminfo and sends fields of interest to UMA.
  // Returns false on errors.  |meminfo_raw| contains the content of
  // /proc/meminfo.
  bool ProcessMeminfo(const std::string& meminfo_raw);

  // Parses meminfo data from |meminfo_raw|.  |fields| is a vector containing
  // the fields of interest.  The order of the fields must be the same in which
  // /proc/meminfo prints them.  The result of parsing fields[i] is placed in
  // fields[i].value.
  bool FillMeminfo(const std::string& meminfo_raw,
                   std::vector<MeminfoRecord>* fields);

  // Schedule a memory use callback in |interval| seconds.
  void ScheduleMemuseCallback(double interval);

  // Calls MemuseCallbackWork, and possibly schedules next callback, if enough
  // active time has passed.  Otherwise reschedules itself to simulate active
  // time callbacks (i.e. wall clock time minus sleep time).
  void MemuseCallback();

  // Reads /proc/meminfo and sends total anonymous memory usage to UMA.
  bool MemuseCallbackWork();

  // Parses meminfo data and sends it to UMA.
  bool ProcessMemuse(const std::string& meminfo_raw);

  // Sends stats for thermal CPU throttling.
  void SendCpuThrottleMetrics();

  // Reads an integer CPU frequency value from sysfs.
  bool ReadFreqToInt(const std::string& sysfs_file_name, int* value);

  // Reads the current OS version from /etc/lsb-release and hashes it
  // to a unsigned 32-bit int.
  uint32_t GetOsVersionHash();

  // Updates stats, additionally sending them to UMA if enough time has elapsed
  // since the last report.
  void UpdateStats(base::TimeTicks now_ticks, base::Time now_wall_time);

  // Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
  void HandleUpdateStatsTimeout();

  // Reports zram statistics.
  bool ReportZram(const base::FilePath& zram_dir);

  // Reads a string from a file and converts it to uint64_t.
  static bool ReadFileToUint64(const base::FilePath& path, uint64_t* value);

  // VARIABLES

  // Test mode.
  bool testing_;

  // Whether the uploader is enabled or disabled.
  bool uploader_active_;

  // Whether or not dbus should be used.
  // If disabled, we will not collect the frequency of crashes.
  bool dbus_enabled_;

  // Root of the configuration files to use.
  std::string config_root_;

  // The metrics library handle.
  MetricsLibraryInterface* metrics_lib_;

  // Timestamps last network state update.  This timestamp is used to
  // sample the time from the network going online to going offline so
  // TimeTicks ensures a monotonically increasing TimeDelta.
  base::TimeTicks network_state_last_;

  // The last time that UpdateStats() was called.
  base::TimeTicks last_update_stats_time_;

  // End time of current memuse stat collection interval.
  double memuse_final_time_;

  // Selects the wait time for the next memory use callback.
  unsigned int memuse_interval_index_;

  // Contain the most recent disk and vm cumulative stats.
  uint64_t read_sectors_;
  uint64_t write_sectors_;
  struct VmstatRecord vmstats_;

  StatsState stats_state_;
  double stats_initial_time_;

  // The system "HZ", or frequency of ticks.  Some system data uses ticks as a
  // unit, and this is used to convert to standard time units.
  uint32_t ticks_per_second_;
  // Used internally by GetIncrementalCpuUse() to return the CPU utilization
  // between calls.
  uint64_t latest_cpu_use_ticks_;

  // Persistent values and accumulators for crash statistics.
  scoped_ptr<PersistentInteger> daily_cycle_;
  scoped_ptr<PersistentInteger> weekly_cycle_;
  scoped_ptr<PersistentInteger> version_cycle_;

  // Active use accumulated in a day.
  scoped_ptr<PersistentInteger> daily_active_use_;
  // Active use accumulated since the latest version update.
  scoped_ptr<PersistentInteger> version_cumulative_active_use_;

  // The CPU time accumulator.  This contains the CPU time, in milliseconds,
  // used by the system since the most recent OS version update.
  scoped_ptr<PersistentInteger> version_cumulative_cpu_use_;

  scoped_ptr<PersistentInteger> user_crash_interval_;
  scoped_ptr<PersistentInteger> kernel_crash_interval_;
  scoped_ptr<PersistentInteger> unclean_shutdown_interval_;

  scoped_ptr<PersistentInteger> any_crashes_daily_count_;
  scoped_ptr<PersistentInteger> any_crashes_weekly_count_;
  scoped_ptr<PersistentInteger> user_crashes_daily_count_;
  scoped_ptr<PersistentInteger> user_crashes_weekly_count_;
  scoped_ptr<PersistentInteger> kernel_crashes_daily_count_;
  scoped_ptr<PersistentInteger> kernel_crashes_weekly_count_;
  scoped_ptr<PersistentInteger> kernel_crashes_version_count_;
  scoped_ptr<PersistentInteger> unclean_shutdowns_daily_count_;
  scoped_ptr<PersistentInteger> unclean_shutdowns_weekly_count_;

  std::string vmstats_path_;
  std::string scaling_max_freq_path_;
  std::string cpuinfo_max_freq_path_;

  base::TimeDelta upload_interval_;
  std::string server_;
  std::string metrics_file_;

  scoped_ptr<UploadService> upload_service_;
};

#endif  // METRICS_METRICS_DAEMON_H_
