428 lines
16 KiB
Diff
428 lines
16 KiB
Diff
From 2d6269e360a654f11370f31ea4faf6ee4012d73f Mon Sep 17 00:00:00 2001
|
||
From: hamster <hujing@isrc.iscas.ac.cn>
|
||
Date: Mon, 14 Nov 2022 03:24:49 +0000
|
||
Subject: [PATCH 04/11] add new dimensions
|
||
|
||
From: @hujing2
|
||
Reviewed-by: @gaoruoshu
|
||
Signed-off-by: @gaoruoshu
|
||
---
|
||
README.md | 16 +++
|
||
atune_collector/collect_data.json | 14 ++
|
||
atune_collector/collect_data.py | 97 ++++++++------
|
||
atune_collector/plugin/monitor/__init__.py | 1 +
|
||
atune_collector/plugin/monitor/common.py | 1 +
|
||
.../plugin/monitor/process/__init__.py | 20 +++
|
||
.../plugin/monitor/process/sched.py | 124 ++++++++++++++++++
|
||
7 files changed, 233 insertions(+), 40 deletions(-)
|
||
create mode 100644 atune_collector/plugin/monitor/process/__init__.py
|
||
create mode 100644 atune_collector/plugin/monitor/process/sched.py
|
||
|
||
diff --git a/README.md b/README.md
|
||
index c121c8d..242f345 100644
|
||
--- a/README.md
|
||
+++ b/README.md
|
||
@@ -50,12 +50,14 @@ python3 collect_data.py [OPTIONS]
|
||
| ---------------- | ------------------------------------- | ------------ | ------------ |
|
||
| network | 待采集的指定网卡 | 字符串 | - |
|
||
| block | 待采集的指定磁盘 | 字符串 | - |
|
||
+| application | 需要采集的应用进程 | 字符串 | - |
|
||
| sample_num | 待采集的次数 | 整型 | >0 |
|
||
| interval | 待采集的间隔时间,单位为秒 | 整型 | >0 |
|
||
| output_dir | 采集完后数据存储的文件路径 | 字符串 | - |
|
||
| workload_type | 采集环境的应用负载类型,用作输出文件名,默认为default | 字符串 | - |
|
||
| collection_items | 需要采集的系统参数项,参见表2 | 列表 | - |
|
||
|
||
+
|
||
最终采集完后,数据将保存为: `${output_dir}/${workload_type}-${finish_timestamp}.csv`
|
||
|
||
表2 collection_items项配置说明
|
||
@@ -76,6 +78,7 @@ collect_data.json文件配置示例:
|
||
{
|
||
"network": "eth0",
|
||
"block": "sda",
|
||
+ "application": "mysqld",
|
||
"sample_num": 20,
|
||
"interval": 5,
|
||
"output_dir": "/var/atuned/collect_data",
|
||
@@ -203,6 +206,19 @@ collect_data.json文件配置示例:
|
||
"metrics": [
|
||
"fd-util"
|
||
]
|
||
+ },
|
||
+ {
|
||
+ "name": "process",
|
||
+ "module": "PROCESS",
|
||
+ "purpose": "SCHED",
|
||
+ "metrics": [
|
||
+ "exec_start",
|
||
+ "vruntime",
|
||
+ "sum_exec_runtime",
|
||
+ "switches",
|
||
+ "voluntary_switches",
|
||
+ "involuntary_switches"
|
||
+ ]
|
||
}
|
||
]
|
||
}
|
||
diff --git a/atune_collector/collect_data.json b/atune_collector/collect_data.json
|
||
index db96501..af286bd 100755
|
||
--- a/atune_collector/collect_data.json
|
||
+++ b/atune_collector/collect_data.json
|
||
@@ -1,6 +1,7 @@
|
||
{
|
||
"network": "eth0",
|
||
"block": "sda",
|
||
+ "application": "firewalld,dockerd",
|
||
"sample_num": 20,
|
||
"interval": 5,
|
||
"output_dir": "/var/atuned/collect_data",
|
||
@@ -140,6 +141,19 @@
|
||
"metrics": [
|
||
"fd-util"
|
||
]
|
||
+ },
|
||
+ {
|
||
+ "name": "process",
|
||
+ "module": "PROCESS",
|
||
+ "purpose": "SCHED",
|
||
+ "metrics": [
|
||
+ "exec_start",
|
||
+ "vruntime",
|
||
+ "sum_exec_runtime",
|
||
+ "switches",
|
||
+ "voluntary_switches",
|
||
+ "involuntary_switches"
|
||
+ ]
|
||
}
|
||
]
|
||
}
|
||
\ No newline at end of file
|
||
diff --git a/atune_collector/collect_data.py b/atune_collector/collect_data.py
|
||
index 1764304..3593db6 100755
|
||
--- a/atune_collector/collect_data.py
|
||
+++ b/atune_collector/collect_data.py
|
||
@@ -18,6 +18,7 @@ import argparse
|
||
import json
|
||
import os
|
||
import time
|
||
+import csv
|
||
|
||
from plugin.plugin import MPI
|
||
|
||
@@ -30,66 +31,80 @@ class Collector:
|
||
self.field_name = []
|
||
self.support_multi_block = ['storage']
|
||
self.support_multi_nic = ['network', 'network-err']
|
||
+ self.support_multi_app = ['process']
|
||
|
||
def parse_json(self):
|
||
"""parse json data"""
|
||
monitors = []
|
||
for item in self.data["collection_items"]:
|
||
- parameters = ["--interval=%s;" % self.data["interval"]]
|
||
- for metric in item["metrics"]:
|
||
- nics = self.data["network"].split(',')
|
||
- blocks = self.data["block"].split(',')
|
||
- if item["name"] in self.support_multi_nic and len(nics) > 1:
|
||
- for net in nics:
|
||
+ if item["name"] in self.support_multi_app and ('application' not in self.data or
|
||
+ self.data["application"] == ""):
|
||
+ continue
|
||
+ if item["name"] in self.support_multi_app:
|
||
+ applications = self.data["application"].split(',')
|
||
+ parameters = ["--interval=%s --app=%s;" %(self.data["interval"], self.data["application"])]
|
||
+ for application in applications:
|
||
+ for metric in item["metrics"]:
|
||
self.field_name.append(
|
||
- "%s.%s.%s#%s" % (item["module"], item["purpose"], metric, net))
|
||
- elif item["name"] in self.support_multi_block and len(blocks) > 1:
|
||
- for block in blocks:
|
||
- self.field_name.append(
|
||
- "%s.%s.%s#%s" % (item["module"], item["purpose"], metric, block))
|
||
- else:
|
||
- self.field_name.append("%s.%s.%s" % (item["module"], item["purpose"], metric))
|
||
- parameters.append("--fields=%s" % metric)
|
||
- if "threshold" in item:
|
||
- parameters.append("--threshold=%s" % item["threshold"])
|
||
+ "%s.%s.%s#%s" % (item["module"], item["purpose"], metric, application))
|
||
+ parameters.append("--fields=%s" % metric)
|
||
+ else:
|
||
+ parameters = ["--interval=%s;" % self.data["interval"]]
|
||
+ for metric in item["metrics"]:
|
||
+ nics = self.data["network"].split(',')
|
||
+ blocks = self.data["block"].split(',')
|
||
+
|
||
+ if item["name"] in self.support_multi_nic and len(nics) > 1:
|
||
+ for net in nics:
|
||
+ self.field_name.append(
|
||
+ "%s.%s.%s#%s" % (item["module"], item["purpose"], metric, net))
|
||
+ elif item["name"] in self.support_multi_block and len(blocks) > 1:
|
||
+ for block in blocks:
|
||
+ self.field_name.append(
|
||
+ "%s.%s.%s#%s" % (item["module"], item["purpose"], metric, block))
|
||
+ else:
|
||
+ self.field_name.append("%s.%s.%s" % (item["module"], item["purpose"], metric))
|
||
+ parameters.append("--fields=%s" % metric)
|
||
+ if "threshold" in item:
|
||
+ parameters.append("--threshold=%s" % item["threshold"])
|
||
+
|
||
parameters.append("--nic=%s" % self.data["network"])
|
||
parameters.append("--device=%s" % self.data["block"])
|
||
monitors.append([item["module"], item["purpose"], " ".join(parameters)])
|
||
return monitors
|
||
|
||
- def save_csv(self, field_data):
|
||
- """save data to csv file"""
|
||
+ def collect_data(self):
|
||
+ """collect data"""
|
||
+ collect_num = self.data["sample_num"]
|
||
+ if int(collect_num) < 1:
|
||
+ os.abort("sample_num must be greater than 0")
|
||
+
|
||
+ mpi = MPI()
|
||
+ monitors = self.parse_json()
|
||
path = self.data["output_dir"]
|
||
if not os.path.exists(path):
|
||
os.makedirs(path, 0o750)
|
||
file_name = "{}-{}.csv".format(self.data.get("workload_type", "default"),
|
||
int(round(time.time() * 1000)))
|
||
- import csv
|
||
+
|
||
+ print("start to collect data, csv path is %s" % os.path.join(path, file_name))
|
||
+ print(" ".join(self.field_name))
|
||
with open(os.path.join(path, file_name), "w") as csvfile:
|
||
writer = csv.writer(csvfile)
|
||
self.field_name.insert(0, "TimeStamp")
|
||
writer.writerow(self.field_name)
|
||
- writer.writerows(field_data)
|
||
+ csvfile.flush()
|
||
+ for _ in range(collect_num):
|
||
+ raw_data = mpi.get_monitors_data(monitors)
|
||
+ float_data = [float(num) for num in raw_data]
|
||
+ str_data = [str(round(value, 3)) for value in float_data]
|
||
+ print(" ".join(str_data))
|
||
+ float_data.insert(0, time.strftime("%H:%M:%S"))
|
||
+ # field_data.append(float_data)
|
||
+ writer.writerow(float_data)
|
||
+ csvfile.flush()
|
||
print("finish to collect data, csv path is %s" % os.path.join(path, file_name))
|
||
|
||
- def collect_data(self):
|
||
- """collect data"""
|
||
- collect_num = self.data["sample_num"]
|
||
- if int(collect_num) < 1:
|
||
- os.abort("sample_num must be greater than 0")
|
||
- field_data = []
|
||
- mpi = MPI()
|
||
- monitors = self.parse_json()
|
||
- print(" ".join(self.field_name))
|
||
- for _ in range(collect_num):
|
||
- raw_data = mpi.get_monitors_data(monitors)
|
||
- float_data = [float(num) for num in raw_data]
|
||
- str_data = [str(round(value, 3)) for value in float_data]
|
||
- print(" ".join(str_data))
|
||
- float_data.insert(0, time.strftime("%H:%M:%S"))
|
||
- field_data.append(float_data)
|
||
- return field_data
|
||
-
|
||
|
||
if __name__ == "__main__":
|
||
default_json_path = "/etc/atune_collector/collect_data.json"
|
||
@@ -100,5 +115,7 @@ if __name__ == "__main__":
|
||
with open(ARGS.config, 'r') as file:
|
||
json_data = json.load(file)
|
||
collector = Collector(json_data)
|
||
- dataset = collector.collect_data()
|
||
- collector.save_csv(dataset)
|
||
+ try:
|
||
+ collector.collect_data()
|
||
+ except KeyboardInterrupt:
|
||
+ print("user stop collect data")
|
||
\ No newline at end of file
|
||
diff --git a/atune_collector/plugin/monitor/__init__.py b/atune_collector/plugin/monitor/__init__.py
|
||
index 6291401..9292071 100755
|
||
--- a/atune_collector/plugin/monitor/__init__.py
|
||
+++ b/atune_collector/plugin/monitor/__init__.py
|
||
@@ -19,6 +19,7 @@ __all__ = [
|
||
"memory",
|
||
"network",
|
||
"performance",
|
||
+ "process",
|
||
"processor",
|
||
"storage",
|
||
"common",
|
||
diff --git a/atune_collector/plugin/monitor/common.py b/atune_collector/plugin/monitor/common.py
|
||
index 12a07f2..d0aa60c 100755
|
||
--- a/atune_collector/plugin/monitor/common.py
|
||
+++ b/atune_collector/plugin/monitor/common.py
|
||
@@ -134,6 +134,7 @@ class Monitor(object):
|
||
"--interval=" to specify period of time
|
||
"--cpu=" to select which cpu
|
||
"--event=" to select which event
|
||
+ "--app" to select which applications
|
||
:returns value: Success, collected info string
|
||
:raises Exceptions: Fail, with info
|
||
"""
|
||
diff --git a/atune_collector/plugin/monitor/process/__init__.py b/atune_collector/plugin/monitor/process/__init__.py
|
||
new file mode 100644
|
||
index 0000000..4c4ceb3
|
||
--- /dev/null
|
||
+++ b/atune_collector/plugin/monitor/process/__init__.py
|
||
@@ -0,0 +1,20 @@
|
||
+#!/usr/bin/python3
|
||
+# -*- coding: utf-8 -*-
|
||
+# Copyright (c) 2022 Huawei Technologies Co., Ltd.
|
||
+# A-Tune is licensed under the Mulan PSL v2.
|
||
+# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
+# You may obtain a copy of Mulan PSL v2 at:
|
||
+# http://license.coscl.org.cn/MulanPSL2
|
||
+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||
+# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||
+# PURPOSE.
|
||
+# See the Mulan PSL v2 for more details.
|
||
+# Create: 2022-10-14
|
||
+
|
||
+"""
|
||
+Init file.
|
||
+"""
|
||
+
|
||
+__all__ = ["sched"]
|
||
+
|
||
+from . import *
|
||
\ No newline at end of file
|
||
diff --git a/atune_collector/plugin/monitor/process/sched.py b/atune_collector/plugin/monitor/process/sched.py
|
||
new file mode 100644
|
||
index 0000000..5289d84
|
||
--- /dev/null
|
||
+++ b/atune_collector/plugin/monitor/process/sched.py
|
||
@@ -0,0 +1,124 @@
|
||
+#!/usr/bin/python3
|
||
+# -*- coding: utf-8 -*-
|
||
+# Copyright (c) 2022 Huawei Technologies Co., Ltd.
|
||
+# A-Tune is licensed under the Mulan PSL v2.
|
||
+# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
+# You may obtain a copy of Mulan PSL v2 at:
|
||
+# http://license.coscl.org.cn/MulanPSL2
|
||
+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||
+# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||
+# PURPOSE.
|
||
+# See the Mulan PSL v2 for more details.
|
||
+# Create: 2022-10-14
|
||
+
|
||
+"""
|
||
+The sub class of the monitor, used to collect the process sched info
|
||
+"""
|
||
+import inspect
|
||
+import logging
|
||
+import subprocess
|
||
+import getopt
|
||
+import re
|
||
+from ..common import Monitor
|
||
+
|
||
+LOGGER = logging.getLogger(__name__)
|
||
+
|
||
+
|
||
+class ProcSched(Monitor):
|
||
+ """To collect the process sched info"""
|
||
+ _module = "PROCESS"
|
||
+ _purpose = "SCHED"
|
||
+ _option = "/proc/{}/sched"
|
||
+
|
||
+ def __init__(self, user=None):
|
||
+ Monitor.__init__(self, user)
|
||
+ self.__cmd = "cat"
|
||
+ self.__interval = 1
|
||
+ self.__applications = []
|
||
+ self.__pids = []
|
||
+
|
||
+ def _get(self, para=None):
|
||
+ output = ""
|
||
+ pids = []
|
||
+ if para is not None:
|
||
+ opts, _ = getopt.getopt(para.split(), None, ['interval=', 'app='])
|
||
+ for opt, val in opts:
|
||
+ if opt in '--interval':
|
||
+ if val.isdigit():
|
||
+ self.__interval = int(val)
|
||
+ else:
|
||
+ err = ValueError(
|
||
+ "Invalid parameter: {opt}={val}".format(
|
||
+ opt=opt, val=val))
|
||
+ LOGGER.error("%s.%s: %s", self.__class__.__name__,
|
||
+ inspect.stack()[0][3], str(err))
|
||
+ raise err
|
||
+ continue
|
||
+ elif opt in '--app':
|
||
+ if val is not None:
|
||
+ self.__applications = val.split(',')
|
||
+ else:
|
||
+ err = ValueError(
|
||
+ "{opt} parameter is none".format(
|
||
+ opt=opt))
|
||
+ LOGGER.error("%s.%s: %s", self.__class__.__name__,
|
||
+ inspect.stack()[0][3], str(err))
|
||
+ raise err
|
||
+
|
||
+ for app in self.__applications:
|
||
+ pid = subprocess.getoutput(
|
||
+ "ps -A | grep {} | awk '{{print $1}}'".format(app)).split()[0]
|
||
+ pids.append(pid)
|
||
+ self.__pids = pids
|
||
+
|
||
+ for pid in self.__pids:
|
||
+ out = subprocess.check_output(
|
||
+ "{cmd} {opt}".format(
|
||
+ cmd=self.__cmd,
|
||
+ opt=self._option.format(pid)).split())
|
||
+ output = output + "" + out.decode()
|
||
+ return output
|
||
+
|
||
+ def decode(self, info, para):
|
||
+ """
|
||
+ decode the result of the operation
|
||
+ :param info: content that needs to be decoded
|
||
+ :param para: command line argument
|
||
+ :returns ret: operation result
|
||
+ """
|
||
+
|
||
+ if para is None:
|
||
+ return info
|
||
+
|
||
+ start = 0
|
||
+ keys = []
|
||
+ ret = ""
|
||
+
|
||
+ opts, _ = getopt.getopt(para.split(), None, ['nic=', 'fields=', 'device='])
|
||
+ for opt, val in opts:
|
||
+ if opt in '--fields':
|
||
+ keys.append(val)
|
||
+ continue
|
||
+
|
||
+ pattern = re.compile(
|
||
+ r"(\w+)\ {1,}\:\ {1,}(\d+.\d+)",
|
||
+ re.I | re.UNICODE | re.MULTILINE)
|
||
+ search_obj = pattern.findall(info)
|
||
+ search_list = []
|
||
+ for obj in search_obj:
|
||
+ if obj[0][:3] == "nr_":
|
||
+ search_list.append(obj[0][3:])
|
||
+ else:
|
||
+ search_list.append(obj[0])
|
||
+ search_list.append(obj[1])
|
||
+ if len(search_obj) == 0:
|
||
+ err = LookupError("Fail to find data")
|
||
+ LOGGER.error("%s.%s: %s", self.__class__.__name__,
|
||
+ inspect.stack()[0][3], str(err))
|
||
+ raise err
|
||
+
|
||
+ while start <= len(self.__applications) * len(keys):
|
||
+ for key in keys:
|
||
+ ret = ret + " " + search_list[search_list.index(key, start)+1]
|
||
+ start = search_list.index(key, start) + 1
|
||
+ return ret
|
||
\ No newline at end of file
|
||
--
|
||
2.27.0
|
||
|