1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2024 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17import os
18import time
19import platform
20from datetime import datetime
21import re
22from pathlib import Path
23import subprocess
24from collections import defaultdict
25
26from resources.global_var import ROOT_CONFIG_FILE
27from log_util import LogUtil
28from io_util import IoUtil
29
30GB_CONSTANT = 1024 ** 3
31MEM_CONSTANT = 1024
32RET_CONSTANT = 0
33MONITOR_TIME_CONSTANT = 30
34SNOP_TIME_CONSTANT = 5
35
36memory_info = [RET_CONSTANT] * 3
37target_keys = ['MemTotal', 'SwapTotal', 'MemFree']
38key_indices = {key: index for index, key in enumerate(target_keys)}
39
40
41class Monitor():
42    def __init__(self):
43        self.now_times = []
44        self.usr_cpus = []
45        self.sys_cpus = []
46        self.idle_cpus = []
47        self.total_mems = []
48        self.swap_mems = []
49        self.free_mems = []
50        self.log_path = ""
51
52    def collect_cpu_info(self):
53        if platform.system() != "Linux":
54            return RET_CONSTANT, RET_CONSTANT, RET_CONSTANT
55
56        try:
57            top_command = ["top", "-bn1"]
58            grep_command = ["grep", "%Cpu(s)"]
59            top_output = subprocess.check_output(top_command, universal_newlines=True)
60            result = subprocess.check_output(grep_command, input=top_output, universal_newlines=True).strip()
61            if result:
62                parts = result.split()
63                if len(parts) >= 5:
64                    usr_cpu_str = parts[1]
65                    sys_cpu_str = parts[3]
66                    idle_cpu_str = parts[7]
67
68                    usr_cpu_str = usr_cpu_str.split(',')[0].rstrip('%')
69                    sys_cpu_str = sys_cpu_str.split(',')[0].rstrip('%')
70                    idle_cpu_str = idle_cpu_str.split(',')[0].rstrip('%')
71
72                    usr_cpu = float(usr_cpu_str) if usr_cpu_str.replace('.', '', 1).isdigit() else RET_CONSTANT
73                    sys_cpu = float(sys_cpu_str) if sys_cpu_str.replace('.', '', 1).isdigit() else RET_CONSTANT
74                    idle_cpu = float(idle_cpu_str) if idle_cpu_str.replace('.', '', 1).isdigit() else RET_CONSTANT
75
76                    self.usr_cpu = usr_cpu
77                    self.sys_cpu = sys_cpu
78                    self.idle_cpu = idle_cpu
79                    return self.usr_cpu, self.sys_cpu, self.idle_cpu
80                else:
81                    return RET_CONSTANT, RET_CONSTANT, RET_CONSTANT
82            else:
83                return RET_CONSTANT, RET_CONSTANT, RET_CONSTANT
84        except subprocess.CalledProcessError:
85            return RET_CONSTANT, RET_CONSTANT, RET_CONSTANT
86
87    def extract_memory_value(self, line: str):
88        match = re.search(r'\d+', line)
89        return int(match.group()) * MEM_CONSTANT if match else RET_CONSTANT
90
91    def get_ret_num(self, line: str):
92        key = line.split(':')[0]
93        if key in target_keys:
94            value = self.extract_memory_value(line)
95            memory_info[key_indices[key]] = value
96
97    def get_linux_mem_info(self):
98        try:
99            with open('/proc/meminfo', 'r') as f:
100                for line in f:
101                    self.get_ret_num(line)
102            return memory_info[0], memory_info[1], memory_info[2]
103        except FileNotFoundError:
104            return RET_CONSTANT, RET_CONSTANT, RET_CONSTANT
105        except Exception as e:
106            print(f"Error occurred while getting memory info: {e}")
107            return RET_CONSTANT, RET_CONSTANT, RET_CONSTANT
108
109    def collect_linux_mem_info(self):
110        total_memory, swap_memory, free_memory = self.get_linux_mem_info()
111        total_mem = round(total_memory / GB_CONSTANT, 1)
112        free_mem = round(free_memory / GB_CONSTANT, 1)
113        swap_mem = round(swap_memory / GB_CONSTANT, 1)
114        return total_mem, free_mem, swap_mem
115
116    def get_current_time(self):
117        now_time = datetime.now().strftime("%H:%M:%S")
118        self.now_times.append(now_time)
119
120    def get_current_cpu(self):
121        usr_cpu, sys_cpu, idle_cpu = self.collect_cpu_info()
122        self.usr_cpus.append(usr_cpu)
123        self.sys_cpus.append(sys_cpu)
124        self.idle_cpus.append(idle_cpu)
125        LogUtil.write_log(self.log_path, f"User Cpu%: {usr_cpu}%", "info")
126        LogUtil.write_log(self.log_path, f"System Cpu%: {sys_cpu}%", "info")
127        LogUtil.write_log(self.log_path, f"Idle CPU%: {idle_cpu}%", "info")
128
129    def get_current_memory(self):
130        total_mem, free_mem, swap_mem = self.collect_linux_mem_info()
131        self.total_mems.append(total_mem)
132        self.free_mems.append(free_mem)
133        self.swap_mems.append(swap_mem)
134        LogUtil.write_log(self.log_path, f"Total Memory: {total_mem}GB", "info")
135        LogUtil.write_log(self.log_path, f"Free Memory: {free_mem}GB", "info")
136        LogUtil.write_log(self.log_path, f"Swap Memory: {swap_mem}GB", "info")
137
138    def get_log_path(self):
139        count = 0
140        config_content = IoUtil.read_json_file(ROOT_CONFIG_FILE)
141        while not os.path.exists(ROOT_CONFIG_FILE) or not config_content.get('out_path', None) and count <= 60:
142            time.sleep(SNAP_TIME_CONSTANT)
143            count += 1
144        return config_content.get('out_path', None)
145
146    def get_disk_usage(self):
147        result = subprocess.run(['df', '-h'], stdout=subprocess.PIPE, text=True)
148        if result.returncode == 0:
149            lines = result.stdout.strip().split('\n')[1:]
150            for line in lines:
151                columns = line.split()
152                if len(columns) > 5:
153                    filesystem, size, used, available, percent, mountpoint = columns[:6]
154                    LogUtil.write_log(self.log_path,
155                    f"Filesystem: {filesystem}, "
156                    f"Size: {size}, "
157                    f"Used: {used}, "
158                    f"Available: {available}, "
159                    f"Use%: {percent}, "
160                    f"Mounted on: {mountpoint}",
161                    "info")
162        else:
163            LogUtil.write_log(self.log_path, f"Error running df command:{result.stderr}", "info")
164
165    def run(self):
166        if platform.system() != "Linux":
167            return
168        out_path = self.get_log_path()
169        self.log_path = os.path.join(out_path, "build.log")
170        self.get_current_time()
171        self.get_current_cpu()
172        self.get_current_memory()
173        self.get_disk_usage()
174