A-Tune-Collector/add-new-dims.patch
gaoruoshu@huawei.com 0bb8fedb32 feature: enable application configs
(cherry picked from commit c9558b4276c94f7fc75fccf1d167f14b53ee1342)
2023-08-07 10:43:26 +08:00

428 lines
16 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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