实战背景
目前服务器跑多盘(30+)性能测试需要将其带宽数据汇总到一个表格中,然性能测试脚本生成的读写test_data.csv在以盘符为命名的文件夹下,如果汇总的话需要在OA电脑上形如打开sda文件夹再打开test_data文件夹,然后再打开如下形式的sdaa_seq_read_data_op.csv数据表格,然后博主这样的文件有30+个文件夹那岂不是我一上午就敲100+次键盘鼠标才能完成这个任务,既费时又费力。这时候博主就想到了要开发一个脚本自动化完成操作,才有个这个课题,如果有相同类似需求需要处理处理多级目录下(文件夹下)的多个CSV表格内容,并将其指定内容汇总到一个自定义表格中这篇文章你肯定不会白看,我将深入浅出每一行代码讲解已开发好的多盘数据处理脚本。
为了更好让大家理解当前需求,请参看如下图所示内容
一. 软硬件环境
硬件环境: 能运行python的笔记本
软件环境: python3.6+、python模块(re、csv、pandas)
1.1 安装python 3.6+
可以点击该蓝色字体在python官网上下载即可,博主用的3.6.8版本,也可以手动复制以下链接进行下载https://www.python.org/downloads/windows/
1.2 安装python模块
在系统的命令行指示符下执行如下指令:
一定要看清不是在python环境下执行,在window命令行中
C:\Users\admin> pip install re
C:\Users\admin> pip install os
C:\Users\admin> pip install pandas
1.3 脚本运行环境设置
因为博主在中使用的是相对路径而不是绝对路径因此可以运行在笔记本下的任意位置,为了方便最好新建一个文件夹将该脚本跟数据文件放一个位置,如图一所示是目录树架构
运行前文件夹形式:
运行后文件夹形式:
在这里插入图片描述
生成的文件以及文件夹说明:
all_seq_read_per.csv:汇总所有read表格中的带宽数据到该文件下
all_seq_write_per.csv:汇总所有write表格中的带宽数据到该文件下
ssd_seq_all:缓存文件夹用于脚本中遍历所有数据.csv文件(最后没删除是为了便于理解教学可以用os.remove掉缓存文件夹)
read_ssd:存放所有test_data下的所有read类CSV文件
write_ssd:存放所有test_data下的所有write类CSV文件
二. python脚本开发及解析
2.1 定义用到的模组
由于脚本涉及文件或文件夹的移动\复制\删除等操作因此需要用的os、shutil模块,另外脚本涉及到对CSV文件操作因此需要引入csv模块以及对DataFrame数据结构操作的pandas模块,由此脚本如下
import re
import os
import shutil
import csv
import pandas as pd
2.2 定义脚本中需要用到的缓存文件夹
如上所述,因为我们需要用到ssd_seq_all、read_ssd、write_ssd文件夹,因此在脚本中我写了一个关于创建文件夹的函数。另外因为我们使用的是相对路径因此需要用cwd函数去创建当前脚本的路径,脚本内容如下
Work_Cmd = os.getcwd()
def set_dir1(filename):
if not os.path.exists(os.path.join(Work_Cmd, filename)):
os.mkdir(os.path.join(Work_Cmd, filename))
else:
print('请注意' + Work_Cmd + '文件夹下已存在' + filename + '请删除' + filename + '后执行该脚本')
set_dir1('ssd_seq_all')
set_dir1('read_ssd')
set_dir1('write_ssd')
os.getcwd: 用来获取当前路径(假设获取到的当前路径为D:\software\pycharm\pythonProject3\Mutil_process_work)
os.path.exists: 用来判断文件存不存在,存在返回True,不存在返回False。在这里是用来判断当前文件夹下是否存在形参filename
os.path.join: 用于路径拼接文件路径,可以传入多个路径。在这里用来代指当前文件夹下的fiename文件夹也就是D:\software\pycharm\pythonProject3\Mutil_process_work\ssd_seq_all(read_ssd\write_ssd)
2.3 提取所有read/write.csv文件到缓存文件夹下
# 将形如sda文件夹中所有的形如sda_seq_read_data_op.csv的文件提取到ssd_seq_all文件夹中
"""一层循环遍历的是sda sdb sdc...文件夹"""
for i in os.listdir():
f = os.path.join(os.path.join(Work_Cmd, i))
ff = os.path.join(f, 'test_data')
"""二层循环遍历的是sda/test_data/ sdb/test_data sdc/test_data下的读写文件形如sdab_seq_read_data_op.csv"""
try:
for q in os.listdir(ff):
shutil.move(os.path.join(ff, q), os.path.join(Work_Cmd, 'ssd_seq_all'))
except FileNotFoundError:
pass
os.listdir:用于返回指定的文件夹包含的文件或文件夹的名字的列表。在这里一层嵌套的os.listdir用来代指D:\software\pycharm\pythonProject3\Mutil_process_work\文件夹下的sda、sdb、sdc文件夹…
f:代指D:\software\pycharm\pythonProject3\Mutil_process_work\sda文件夹
ff:代指D:\software\pycharm\pythonProject3\Mutil_process_work\sda\test_data文件夹
try…except代码段:用于测试写的代码是否符合返回值要求,在这里表示我当前文件夹除了有数据文件夹还有多盘数据处理_ing.py脚本因此在循环遍历的时候脚本会尝试寻找
D:\software\pycharm\pythonProject3\Mutil_process_work\多盘数据处理_ing.py\test_data这个文件夹,因为没有这个文件夹所以会报FileNotFoundError错误,而使用try…except代码段你就可以自定义当遇到该问题FileNotFoundError时要怎么处理这里我用的是PASS,因为是已知问题。
shutil.move:文件或目录(源)递归移动到另一个位置(目标)并返回目标。在这里就是相当于将
'D:\software\pycharm\pythonProject3\Mutil_process_work\sda\test_data\sda_seq_read_data_op.csv(sda_seq_write_data_op.csv)’的文件移动到’D:\software\pycharm\pythonProject3\Mutil_process_work\sd_seq_all’文件夹中
2.4 将全部数据表格放入对应的读写文件夹中
在这里因为我要对读还有写数据均进行移动操作,因此为了代码的简洁工整性我编写了函数process_per_csv来做这项工作。
# 遍历所有的形如sdaa_seq_read_data_op.csv的文件,并将*read*.csv/*write*.csv文件放入对应的read_ssd/write_ssd文件夹下
def process_per_csv(compiles, working_ssd):
read_per_lists = []
for d in os.listdir(os.path.join(Work_Cmd, 'ssd_seq_all')):
# pat = re.compile('sd\w+_seq_read_data_op.csv')
pat = re.compile(compiles)
"""返回read_csv是列表形式,单独str形式是'[\'sdaa_seq_read_data_op.csv\']'我们需要的是字符串形式是sdaa_seq_read_data_op.csv"""
read_csv = pat.findall(d)
read_per_lists.append(read_csv) # read_per_list中会包含空列表
"""处理read_per_list里面的空列表,形如read_per_list = [['sdaa_seq_read_data_op.csv'], ['sdab_seq_read_data_op.csv']]"""
read_per_list = [x for x in read_per_lists if x != []]
for j in read_per_list:
shutil.move(os.path.join(os.path.join(os.path.join(Work_Cmd), 'ssd_seq_all'), j[0]),
os.path.join(Work_Cmd, working_ssd))
process_per_csv('sd\w+_seq_read_data_op.csv', 'read_ssd')
process_per_csv('sd\w+_seq_write_data_op.csv', 'write_ssd')
re.compile :编译正则表达式模式,返回一个对象。'在这里sd\w+_seq_read_data_op.csv中\w表示任意字母或者数字即a-z、A-Z、0-9;+号表示匹配一个及一个以上.'所有该正则表示类似sda_seq_read_data_op.csv的形式。想不起来的可以稍微回忆下正则形式
re.findall:查询字符串中某个正则表达式全部的情况,返回的是一个符合正则表达式的结果列表。是match对象,不是迭代对象,可直接打印,不需要group(),语法格式与match()类似。在这里表示的是匹配所有类似sda_seq_read_data_op.csv的文件名,在这里read_csv返回的是一个形如[[sda_seq_read_data_op.csv], [], [sdb_seq_read_data_op.csv]]的列表一定要注意不是简简单单的字符串,当时博主想当然的像将read_csv变为一个字符串str(read_csv)这是不对的
[x for x in read_per_lists if x != []]:该逻辑推到是为常见的剔除空列表的形式。在这里表示遍历read_per_lists列表的所有元素,判断形参x不等于[]“空列表”,则将该形参x的值赋给read_per_list列表中的元素
j[0] :由以上代码可以得知read_per_list列表中的每个元素都是形如[[sda_seq_read_data_op.csv], [sdb_seq_read_data_op.csv],…]的一个列表。因此我们想要得到这个read_per_list列表中[sda_seq_read_data_op.csv]列表中的元素,仅需要切片index=0的元素(毕竟里面就一个元素)
shutil.move:这里的shutil.move指的是将read.csv/write.csv文件放入对应的read_ssd/write_ssd文件夹下
2.5 提取数据表格中的内容到系统缓存中
因为我们需要对CSV表格里的二维数据进行操作,因此我这里将需要提取CSV二维数据内存的操作单独写完一个函数,并将文件的sd*字段赋值给表格中的driver
def load_csv(file_name):
df1 = pd.read_csv(file_name)
# print(file_name.split('.')[0])形如D:\software\pycharm\pythonProject3\Mutil_process_work\read_ssd\sdaa_seq_read_data_op
df1['driver'] = file_name.split('.')[0].split('\\')[-1].split('_')[0] # Server形如sda sdb
# df['Server'] = file_name.split('.')[0] # df = sdab_seq_read_data_op sdac_seq_read_data_op
return df1
load_csv:该函数用于对形参文件里面的内容进行读取并返回当前值
pd.read_csv:表示对传入的形参filename文件里面的内容进行读取
df1[*]:单维度选取即行(列)选取。在这里表示我将文件名中形如sda的字段赋值给df1中行(列)名为driver的它
2.6 整合表格的数据内容
在这里是本脚本的核心,为了代码的简洁以及工整性我编写了函数来实现我将多数据文件中的关键字段信息整合到汇总表格中。在看本段代码之前如果对数据透视表以及pandas模块DataFreme数据类型以及pivot函数没有了解认知的话,可以参看以下链接完再看本段代码可能会很好理解
pandas模块:
https://www.cnblogs.com/hhaostudy/p/16025398.html
Datafreme数据类型:
https://blog.csdn.net/m0_60392490/article/details/121184960
pivot函数:
https://blog.csdn.net/superY_26/article/details/112689493
def per_data_process(sct_work, out_csv):
"""对read/write.csv里面的数据带宽数据进行操作"""
set_write_csv = os.listdir(os.path.join(Work_Cmd, sct_work))
# csv_file_names = [fn for fn in file_names if fn[-4:] == '.csv'] 匹配目录下的CSV文件格式
"""将CSV表格中的数据导入到df中DataFrame数据格式"""
df = pd.DataFrame().append([load_csv(os.path.join(os.path.join(Work_Cmd, sct_work), fn)) for fn in set_write_csv])
"""将透视表格式化成以'Block size', 'IO type', 'WR/RD', 'Jobs', 'Queue Depth' 为标签, driver为列表头, Bandwidth为值的csv表格"""
df = df.pivot(index=['Block size', 'IO type', 'WR/RD', 'Jobs', 'Queue Depth'], columns='driver',
values='Bandwidth(kB/s)')
"""将数据表导入到自定义的csv文本"""
df.to_csv(out_csv)
per_data_process('write_ssd', 'all_seq_write_per.csv')
per_data_process('read_ssd', 'all_seq_read_per.csv')
os.listdir:这里的os.listdir表示将write_ssd/read_ssd文件夹下的文件名以列表的形式返回赋值给变量set_write_csv
pd.DataFrame().append:表示对原有的DataFrame数据格式内容再添加(append)内容。再这里表示对形参fn推导式里面的load_csv中的内容进行追加写入,也就是将不同csv表格里面的内容汇总给变量df
df.pivot:对现有内容进行数据重塑类似Excel中数据透视表操作。(Ps:如果对操作数据透视表没有概念的话这边可能会有点难理解)在这里表示对df的DataFrame数据以’Block size’, ‘IO type’, ‘WR/RD’, ‘Jobs’, 'Queue Depth’字段为标签,driver字段为列,Bandwidth(kB/s)字段为值进行重塑
三. 总结
最终博主脚本运行完之后的文件内容形式如下图所示:
以上内容是博主这边处理日常测试工作中遇到的实际问题。通过python去处理多级目录下(文件夹下)的多个CSV表格内容,并将其指定内容汇总到一个自定义表格中,这种场景我相信很多人会遇到很多类似的问题,比如运维工程师管理上百台服务器收集以服务器名称命名的系统健康日志,日常科研中用comsol、matlab处理的以时间命名的文件夹数据集等等,均可以通过细微修改脚本中的关键字段来实现汇总操作。
如果有想要尝试跟实践的小伙伴蓝色字体多盘数据处理_ing.py及实验数据来下载或者点击以下链接进行下载
https://download.csdn.net/download/qq_41901686/86514032