#!/usr/bin/python
# -*- coding: utf-8 -*-

# This file is part of Cockpit.
#
# Copyright (C) 2016 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import os

import parent
from testlib import *

def read_mem_info(machine):
    info = { }
    for l in machine.execute("cat /proc/meminfo").splitlines():
        (name, value) = l.strip().split(":")
        if value.endswith("kB"):
            info[name] = int(value[:-2])*1024
        else:
            info[name] = int(value)
    return info

class TestMetrics(MachineCase):

    def check_host_metrics(self):
        b = self.browser
        m = self.machine

        b.eval_js("""
          ph_plot_timestamp = function (sel) {
            var data = $(sel).data("flot_data")[0]['data'];
            return data[data.length-1][0];
          }
        """)

        b.eval_js("""
          ph_plot_timestamp_is = function (sel, ts) {
            return ph_plot_timestamp(sel, 0) >= ts;
          }
        """)

        def wait_for_plot(sel, seconds):
            now = b.call_js_func("ph_plot_timestamp", sel)
            b.wait_js_func("ph_plot_timestamp_is", sel, now+20*1000)

        # When checking whether the plots show the expected results,
        # we look for a segment of the data of a certain duration
        # whose average is in a certain range.  Otherwise any short
        # outlier will make us miss the expected plateau.  Such
        # outliers happen frequently with the CPU plot.

        b.eval_js("""
          ph_plateau = function (data, min, max, duration, label) {
              var i, j;
              var sum;  // sum of data[i..j]

              sum = 0;
              i = 0;
              for (j = 0; j < data.length; j++) {
                  sum += data[j][1];
                  while (i < j && (data[j][0] - data[i][0]) > duration * 1000) {
                      avg = sum / (j - i + 1);
                      if ((min === null || avg >= min) && (max === null || avg <= max))
                          return true;
                      sum -= data[i][1];
                      i++;
                  }
              }
            return false;
          }
        """)

        b.eval_js("""
          ph_flot_data_plateau = function (sel, min, max, duration, label) {
            return ph_plateau($(sel).data("flot_data")[0]['data'], min, max, duration, label);
          }
        """)

        # CPU.  We have to measure the actual percentage independently
        # and check whether the plot matches it.  This is because we
        # can not reliably create 100% load in a virtual machine since
        # we only get as much as the qemu process itself gets.

        m.upload([ "verify/files/measure-cpu" ], "/var/lib/testvm/")

        cpu_start = m.execute("/var/lib/testvm/measure-cpu start")
        cpu_pid = m.spawn("while true; do true; done", "cpu-load.log")
        wait_for_plot("#server_cpu_graph", 20)
        m.execute("kill %d" % cpu_pid)
        cpu_percentage = float(m.execute("/var/lib/testvm/measure-cpu stop '%s'" % cpu_start))

        print "Measured CPU", cpu_percentage
        self.assertTrue(b.call_js_func("ph_flot_data_plateau", "#server_cpu_graph",
                                       cpu_percentage*0.50, cpu_percentage*1.50, 15, "cpu"));

        # Memory.  We assume that the machine has had a reasonably
        # stable memory situation for the last 20 seconds and we just
        # check whether we see that in the plot.
        #
        meminfo = read_mem_info(m)
        mem_used = meminfo['MemTotal'] - meminfo['MemFree']
        b.wait_js_func("ph_flot_data_plateau", "#server_memory_graph", mem_used*0.95, mem_used*1.05, 15, "mem");

        # Disk.  Anything above 100 kiB/s is good for now.
        #
        m.add_disk("10M", serial="MYSERIAL")
        disk_pid = m.spawn("while true; do dd if=/dev/sda of=/dev/sda; done", "load-disk.log")
        b.wait_js_func("ph_flot_data_plateau", "#server_disk_io_graph", 100*1024, None, 5, "disk io");
        m.execute("kill %d" % disk_pid)

        # Network.  Anything above 300 kb/s is good for now.
        #
        identity = m._calc_identity()
        m.upload([ identity ], "/var/tmp")
        net_pid = m.spawn("ssh -o StrictHostKeyChecking=no -i /var/tmp/{1} {0} cat /dev/zero >/dev/null".format(m.address, os.path.basename(identity)), "load-net.log")
        b.wait_js_func("ph_flot_data_plateau", "#server_network_traffic_graph", 300*1000, None, 5, "net io");
        m.execute("kill %d" % net_pid)

    @skipImage("No PCP available on Atomic", "fedora-atomic", "rhel-atomic", "continuous-atomic")
    @skipImage("No PCP available on Debian testing/unstable", "debian-testing")
    def testPcp(self):
        m = self.machine

        self.login_and_go("/system")

        self.check_host_metrics()
        m.execute("pgrep cockpit-pcp")

    def testInternal(self):
        m = self.machine

        m.execute("! [ -f /usr/libexec/cockpit-pcp ] || rm /usr/libexec/cockpit-pcp")
        m.execute("! [ -f /usr/lib/cockpit/cockpit-pcp ] || rm /usr/lib/cockpit/cockpit-pcp")

        self.login_and_go("/system")

        self.check_host_metrics()
        m.execute("! pgrep cockpit-pcp")

if __name__ == '__main__':
    test_main()
