• 20200310 CMDB基础设计


    昨日回顾

    1.补充了一些 linux 相关的命令,全部都是要求大家记得,希望大家多敲多练 
    
    2.传统运维和自动化运维的比较 传统运维在上线的时候,会做一些重复事情。自动化运维就是设计各种系统,然后所有的后 续操作,全部都是通过这些系统完成,不需要运维人员再一次的介入 传统运维更强调人工的干预操作,而自动化运维不需要再一次人工介入操作 docker + k8s 用GO写的 
    
    3.自动化运维中非常重要的一个系统,基石项目:CMDB cmdb 管理服务器的所有的基本信息, 采集的信息,包括:服务器的主机名,IP,操作系统版本,磁盘信息,CPU信息,网卡信息 等,人工输入的信息,包括:服务器的机房,几层,机架,机架的第几层,服务器的管理人员 
    
    4.CMDB的两套设计方案 
    	agent方案 
    		将每一台服务器上部署相同的采集脚本,每天晚上定点执行这个采集的项目脚本,采集 完成之后,会将采集到的结果通过requests模块下面的post方法发送给服务端的API 接口,API拿到数据之后,会对数据进行二次分析,然后将得到的数据入库,最后, django启动一个webserver,从数据库中将数据展示出来 
    		上述方案的缺点:当我们需要在增加服务器的时候,将脚本再一次的部署到服务器上,比较麻烦 
    	
    	ssh类方案 
    		搞一台中控机,中控机上安装paramiko模块,登录到待采集的服务器上,执行相关的 linux命令。采集完成之后,会将采集到的结果通过requests模块下面的post方法 发送给服务端的API接口,API拿到数据之后,会对数据进行二次分析,然后将得到的 数据入库,最后,django启动一个webserver,从数据库中将数据展示出来
    

    Paramiko介绍

    安装
    	pip3 install pycrypto
    	pip3 install paramiko
    

    paramiko包含两个核心组件:SSHClient和SFTPClient。

    • SSHClient的作用类似于Linux的ssh命令,是对SSH会话的封装,该类封装了传输(Transport),通道(Channel)及SFTPClient建立的方法(open_sftp),通常用于执行远程命令。
    • SFTPClient的作用类似与Linux的sftp命令,是对SFTP客户端的封装,用以实现远程文件操作,如文件上传、下载、修改文件权限等操作。
    # Paramiko中的几个基础名词:
     
    1、Channel:是一种类Socket,一种安全的SSH传输通道;
    2、Transport:是一种加密的会话,使用时会同步创建了一个加密的Tunnels(通道),这个Tunnels叫做Channel;
    3、Session:是client与Server保持连接的对象,用connect()/start_client()/start_server()开始会话。
    

    获取信息

    import paramiko
    # 创建SSH对象
    ssh = paramiko.SSHClient()
    # 允许连接不在know_hosts文件中的主机
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 连接服务器
    ssh.connect(hostname='192.168.79.131', port=22, username='root', password='root')
    
    # 执行命令
    stdin, stdout, stderr = ssh.exec_command(cmd)
    # 获取命令结果
    result = stdout.read()
    
    # 关闭连接
    ssh.close()
    

    CMDB

    目标:

    同时实现这两套方案,好处是:想用哪一套方案,就可以根据配置选项来灵活的进行切换
    

    采集端client的目录设计

    • bin : 整个项目的启动入口文件 start.py
    • src/core : 整个项目的源代码目录
    • lib : 项目中第三方的库文件
    • conf : 配置文件
    • log : 写代码的时候,一定要打日志。但是日志文件的位置一定不再这个项目中的
    • test: 测试使用的文件

    高级配置文件

    参考Django的配置,目标:

    from django.conf import global_settings, settings print(settings.LANGUAGE_CODE)
    

    通过settings这个对象,既能点出来用户自定义的配置,又能点出来高级的默认配置

    代码

    # 导入配置
    from conf import settings
    from . import global_settings
    
    # 进行整合
    class MySettings():
        # 在init方法中整合自定制的配置和默认的配置
        def __init__(self):
            # 整合高级的配置文件
            for k in dir(global_settings):
                if k.isupper():
                    v = getattr(global_settings,k)
                    setattr(self,k,v)
    
            # 整合自定义的配置文件
            for k in dir(settings):
                if k.isupper():
                    v = getattr(settings,k)
                    setattr(self,k,v)
    
    
    settings = MySettings()
    

    采集插件具体的思路

    敏捷开发

    快速上线开发一个项目,迅速上线.然后根据代码进行	
    
    • 第一版的核心代码
    #### 1.两套方案采集ip信息 
    if settings.MODE == 'agent': 
        res = subprocess.getoutput('ifconfig') 
        print(res) 
        
    elif settings.MODE == 'ssh': 
        import paramiko # 创建SSH对象 
        ssh = paramiko.SSHClient()
        # 允许连接不在know_hosts文件中的主机 
    	ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 
        # 连接服务器 
        ssh.connect(hostname='192.168.79.131', port=22, username='root', password='root') 
        # 执行命令 
        stdin, stdout, stderr = ssh.exec_command('ifconfig') 
        # 获取命令结果 
        result = stdout.read() 
        print(result) 
        # 关闭连接 
        ssh.close()
    

    第一版更多的使用if else来进行判断编码的方式使用面向过程的方式进行编程

    存在的缺点:

    1. low
    2. 代码的扩展性差
    3. 不符合高内聚低耦合的原则
      • 写函数类的时候,代码与该函数的功能一致
    编码规范
    	1.变量名与值中间的等号,要有空格
    	2.变量名,函数名,类名命名风格必须一致
    	3.变量名,函数名,类名命名要有意义
    	4.函数体代码不超过50行
    
    • 第二版改进方案

    可插拔式的采集方式

    参考Django的中间件
    

    img

    start启动文件

    from src.plugins import PluginsManager
    
    if __name__ == '__main__':
        PluginsManager().execute()
    

    plugins文件中的__init__文件 (核心管理代码)

    #-*- coding: utf-8 -*-
    #!/usr/bin/env python3
    
    ' 包初始化 '
    
    __author__ = 'Fwzzz'
    
    from lib.conf.config import settings
    import importlib
    
    
    # 主要进行管理插件的类
    class PluginsManager():
        def __init__(self):
            self.plugins_dict = settings.PLUGINS_DICT
    
    
        # 从配置文件中读取采集的插件配置,执行每一个插件类对应的方法
        def execute(self):
            ret = {}
            # 1.循环读取配置中的values
            for k,v in self.plugins_dict.items():
                '''
                k : base, nic, cpu
                v : src.plugins.base.Base
                '''
                # 2.分析采集类的路径   [src.plugins.base, Base]
                module_path, class_name = v.rsplit('.',1)
    
                # 3.导入模块的路径  import_module导入字符串的模块路径
                module = importlib.import_module(module_path)
                # print(module)
    
                # 4.从模块中导入类
                cls = getattr(module, class_name)
                # print(cls)
    
                # 5.实例化类,执行类对应的具体采集方法 (每个类都是process方法)
                res = cls().process()
                ret[k] = res
    
            print(ret)
    
    
        def command_func(self):
            pass
    

    settings文件中定义

    # 参考Django中间件进行设置
    PLUGINS_DICT = {
        'base':'src.plugins.base.Base',
        'cpu':'src.plugins.cpu.Cpu',
        'disk':'src.plugins.disk.Disk',
        'nic':'src.plugins.nic.Nic',
    }
    

    base 获取硬件信息

    class Base():
        def process(self):
            return 'base...'
    

    优化process

    process代码冗余度过高

    解决方法:

    • 继承一个基类,其他的类执行这个基类中的方法 (上述解决方案存在的问题是:以后新增一个子类时候,都需要继承基类的 )
    • 将函数名当成参数传入到另一个函数中 ( 此时传递的函数名实际上是一个内存地址,函数名加括号,代表执行一个函数)

    **process 具体的分析代码 **

    files文件夹:文件里面的内容,是执行linux命令得到的结果

    debug模式

    • debug 为True, 代表此时是测试开发阶段,数据源从files文件夹的对应的文件中读取
    • debug 为FALSe, 代表此时是上线模式, 数据源就是执行具体的linux命令即可

    代码

    启动文件start

    from src.plugins import PluginsManager
    
    if __name__ == '__main__':
        # 启动执行execute()方法获取服务器的数据 (定义返回的是字典)
        ret = PluginsManager().execute()
    
        for k, v in ret.items():
            print(k, v)
    

    自定义设置文件 settings

    import  os
    
    # 获取基础文件路径
    BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    USER = 'root'
    
    # 切换获取服务器信息的方式
    MODE = 'ssh' # anget
    
    
    # True 代表开发测试阶段, False 代表上线  (自定义文件便于测试)
    DEBUG = True
    
    
    # 指定获取的服务器信息的内容  (参考django的中间件方式)
    PLUGINS_DICT = {
        'basic': 'src.plugins.basic.Basic',
        'cpu': 'src.plugins.cpu.Cpu',
        'disk': 'src.plugins.disk.Disk',
        'nic': 'src.plugins.nic.Nic',
    }
    

    全局默认配置 global_settings

    ' 全局配置 '
    
    __author__ = 'Fwzzz'
    
    # 设置默认的配置,用户自定义的配置会覆盖全局的
    USER = 'qqq'
    

    整合配置 config

    ' 整合全局与自定义配置文件 '
    
    __author__ = 'Fwzzz'
    
    # 导入配置文件
    from  conf import  settings
    from . import global_settings
    
    class MySettings():
        # 在init方法中整合自定制的配置和默认的配置
        def __init__(self):
            # 先整合高级的配置文件
            for k in dir(global_settings):  # 循环出global设置中的所有属性
                if k.isupper():             # 判读大写
                    v = getattr(global_settings, k) # 反射获取名称对应的值
                    setattr(self, k, v)     # setattr设置属性
                    
            # 整合自定制的配置文件
            for k in dir(settings):
                if k.isupper():
                    v = getattr(settings, k)
                    setattr(self, k, v)
                    
    
    # 实例化
    setting = MySettings()
    

    获取服务器的信息

    __init__初始化获取信息

    # 导入整合配置
    from lib.conf.config import setting
    import importlib
    
    
    # 用于管理插件的类
    class PluginsManager():
        def __init__(self):
            # 初始化,获取setting中的配置
            self.plugins_dict = setting.PLUGINS_DICT    # 规定获取服务器信息
            self.settings = setting.MODE    # 获取服务器信息的方式
            self.debug = setting.DEBUG      # 开发上线模式
    
    
        # 从配置文件中读取采集的插件配置,执行插件类中对应的方法
        def execute(self):
            # 1.循环获取配置中的value值
            ret = {}
            for k, v in self.plugins_dict.items():
                '''
                自定义的配置,k是想要获取的信息名,v是具体执行文件路径
                '''
                # 2.分析采集类的路径
                '''
                获得方法的路径信息与具体类名
                '''
                module_path, class_name = v.rsplit('.', 1)
    
                # 3.获取导入模块的路径 import_module: 导入字符串的模块路径
                module = importlib.import_module(module_path)
    
                # 4.从模块中获取导入类
                cls = getattr(module,class_name)
    
                # 实例化类,执行类对应的具体采集方法  (将类comand_func传入获取方法中,减少代码冗余)
                res = cls().process(self.comand_func, self.debug)
                # 执行注册文件中的每一个方法,并保存信息到字典中
                ret[k] = res
    
            return ret
    
    
        # 定义获取信息的方法
        def command_func(self,cmd):
            # 判断当前获取的方式
            if self.settings == 'agent':
                import subprocess
                res = subprocess.getoutput(cmd)
                return res
    
    
            elif self.settings == 'ssh':
                import paramiko
                # 创建ssh对象
                ssh = paramiko.SSHClient()
                # 允许连接不在know_hosts文件中的主机
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                # 连接服务器
                ssh.connect(hostname='192.168.79.131', port=22, username='root', password='root')
    
                # 执行命令   (传入的cmd命令)
                stdin, stdout, stderr = ssh.exec_command(cmd)
                # 获取命令结果
                result = stdout.read()
    
                # 关闭连接
                ssh.close()
    
                return result
    
    

    basic.py

    class Basic():
        def __init__(self):
            pass
        def process(self, command_func, debug):
            # 开发测试阶段
            if debug:
                output = {
                    'os_platform': "linux",
                    'os_version': "CentOS release 6.6 (Final)
    Kernel 
     on an m",
                    'hostname': 'c1.com'
                }
            # 上线阶段
            else:
                output = {
                    'os_platform': command_func("uname").strip(),
                    'os_version': command_func("cat /etc/issue").strip().split('
    ')[0],
                    'hostname': command_func("hostname").strip(),
                }
    
            return output
    

    disk.py

    #-*- coding: utf-8 -*-
    #!/usr/bin/env python3
    
    ' 硬盘相关 '
    
    __author__ = 'Fwzzz'
    
    import re
    import os
    from lib.conf.config import setting
    
    class Disk():
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(setting.BASEDIR, 'files/disk.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("sudo MegaCli -PDList -aALL")
            return self.parse(output)
    
    
        def parse(self, content):
            """
            解析shell命令返回结果
            :param content: shell 命令结果
            :return:解析后的结果
            """
            response = {}
            result = []
            for row_line in content.split("
    
    
    
    "):
                result.append(row_line)
            for item in result:
                temp_dict = {}
                for row in item.split('
    '):
                    if not row.strip():
                        continue
                    if len(row.split(':')) != 2:
                        continue
                    key, value = row.split(':')
                    name = self.mega_patter_match(key)
                    if name:
                        if key == 'Raw Size':
                            raw_size = re.search('(d+.d+)', value.strip())
                            if raw_size:
    
                                temp_dict[name] = raw_size.group()
                            else:
                                raw_size = '0'
                        else:
                            temp_dict[name] = value.strip()
                if temp_dict:
                    response[temp_dict['slot']] = temp_dict
            return response
    
        @staticmethod
        def mega_patter_match(needle):
            grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}
            for key, value in grep_pattern.items():
                if needle.startswith(key):
                    return value
            return False
    

    board.py

    #-*- coding: utf-8 -*-
    #!/usr/bin/env python3
    
    '  '
    
    __author__ = 'Fwzzz'
    
    
    import os
    from lib.conf.config import settings
    
    
    
    class Board(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/board.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("sudo dmidecode -t1")
            return self.parse(output)
    
        def parse(self, content):
    
            result = {}
            key_map = {
                'Manufacturer': 'manufacturer',
                'Product Name': 'model',
                'Serial Number': 'sn',
            }
    
            for item in content.split('
    '):
                row_data = item.strip().split(':')
    
                if len(row_data) == 2:
                    if row_data[0] in key_map:
                        result[key_map[row_data[0]]] = row_data[1].strip() if row_data[1] else row_data[1]
    
            return result
    
    

    memory.py

    #-*- coding: utf-8 -*-
    #!/usr/bin/env python3
    
    '  '
    
    __author__ = 'Fwzzz'
    
    
    import os
    from lib import convert
    from lib.conf.config import settings
    
    
    class Memory(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/memory.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("sudo dmidecode  -q -t 17 2>/dev/null")
    
            return self.parse(output)
    
        def parse(self, content):
            """
            解析shell命令返回结果
            :param content: shell 命令结果
            :return:解析后的结果
            """
            ram_dict = {}
            key_map = {
                'Size': 'capacity',
                'Locator': 'slot',
                'Type': 'model',
                'Speed': 'speed',
                'Manufacturer': 'manufacturer',
                'Serial Number': 'sn',
    
            }
            devices = content.split('Memory Device')
            for item in devices:
                item = item.strip()
                if not item:
                    continue
                if item.startswith('#'):
                    continue
                segment = {}
                lines = item.split('
    	')
                for line in lines:
                    if not line.strip():
                        continue
                    if len(line.split(':')):
                        key, value = line.split(':')
                    else:
                        key = line.split(':')[0]
                        value = ""
                    if key in key_map:
                        if key == 'Size':
                            segment[key_map['Size']] = convert.convert_mb_to_gb(value, 0)
                        else:
                            segment[key_map[key.strip()]] = value.strip()
    
                ram_dict[segment['slot']] = segment
    
            return ram_dict
    

    nic.py

    #-*- coding: utf-8 -*-
    #!/usr/bin/env python3
    
    '  '
    
    __author__ = 'Fwzzz'
    
    
    import os
    import re
    from lib.conf.config import settings
    
    
    class Nic(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/nic.out'), 'r', encoding='utf-8').read()
                interfaces_info = self._interfaces_ip(output)
            else:
                interfaces_info = self.linux_interfaces(command_func)
    
            self.standard(interfaces_info)
    
            return interfaces_info
    
        def linux_interfaces(self, command_func):
            '''
            Obtain interface information for *NIX/BSD variants
            '''
            ifaces = dict()
            ip_path = 'ip'
            if ip_path:
                cmd1 = command_func('sudo {0} link show'.format(ip_path))
                cmd2 = command_func('sudo {0} addr show'.format(ip_path))
                ifaces = self._interfaces_ip(cmd1 + '
    ' + cmd2)
            return ifaces
    
        def which(self, exe):
            def _is_executable_file_or_link(exe):
                # check for os.X_OK doesn't suffice because directory may executable
                return (os.access(exe, os.X_OK) and
                        (os.path.isfile(exe) or os.path.islink(exe)))
    
            if exe:
                if _is_executable_file_or_link(exe):
                    # executable in cwd or fullpath
                    return exe
    
                # default path based on busybox's default
                default_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin'
                search_path = os.environ.get('PATH', default_path)
                path_ext = os.environ.get('PATHEXT', '.EXE')
                ext_list = path_ext.split(';')
    
                search_path = search_path.split(os.pathsep)
                if True:
                    # Add any dirs in the default_path which are not in search_path. If
                    # there was no PATH variable found in os.environ, then this will be
                    # a no-op. This ensures that all dirs in the default_path are
                    # searched, which lets salt.utils.which() work well when invoked by
                    # salt-call running from cron (which, depending on platform, may
                    # have a severely limited PATH).
                    search_path.extend(
                        [
                            x for x in default_path.split(os.pathsep)
                            if x not in search_path
                        ]
                    )
                for path in search_path:
                    full_path = os.path.join(path, exe)
                    if _is_executable_file_or_link(full_path):
                        return full_path
    
            return None
    
        def _number_of_set_bits_to_ipv4_netmask(self, set_bits):  # pylint: disable=C0103
            '''
            Returns an IPv4 netmask from the integer representation of that mask.
    
            Ex. 0xffffff00 -> '255.255.255.0'
            '''
            return self.cidr_to_ipv4_netmask(self._number_of_set_bits(set_bits))
    
        def cidr_to_ipv4_netmask(self, cidr_bits):
            '''
            Returns an IPv4 netmask
            '''
            try:
                cidr_bits = int(cidr_bits)
                if not 1 <= cidr_bits <= 32:
                    return ''
            except ValueError:
                return ''
    
            netmask = ''
            for idx in range(4):
                if idx:
                    netmask += '.'
                if cidr_bits >= 8:
                    netmask += '255'
                    cidr_bits -= 8
                else:
                    netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits)))
                    cidr_bits = 0
            return netmask
    
        def _number_of_set_bits(self, x):
            '''
            Returns the number of bits that are set in a 32bit int
            '''
            # Taken from http://stackoverflow.com/a/4912729. Many thanks!
            x -= (x >> 1) & 0x55555555
            x = ((x >> 2) & 0x33333333) + (x & 0x33333333)
            x = ((x >> 4) + x) & 0x0f0f0f0f
            x += x >> 8
            x += x >> 16
            return x & 0x0000003f
    
        def _interfaces_ip(self, out):
            '''
            Uses ip to return a dictionary of interfaces with various information about
            each (up/down state, ip address, netmask, and hwaddr)
            '''
            ret = dict()
            right_keys = ['name', 'hwaddr', 'up', 'netmask', 'ipaddrs']
    
            def parse_network(value, cols):
                '''
                Return a tuple of ip, netmask, broadcast
                based on the current set of cols
                '''
                brd = None
                if '/' in value:  # we have a CIDR in this address
                    ip, cidr = value.split('/')  # pylint: disable=C0103
                else:
                    ip = value  # pylint: disable=C0103
                    cidr = 32
    
                if type_ == 'inet':
                    mask = self.cidr_to_ipv4_netmask(int(cidr))
                    if 'brd' in cols:
                        brd = cols[cols.index('brd') + 1]
                return (ip, mask, brd)
    
            groups = re.compile('
    ?
    \d').split(out)
            for group in groups:
                iface = None
                data = dict()
    
                for line in group.splitlines():
                    if ' ' not in line:
                        continue
                    match = re.match(r'^d*:s+([w.-]+)(?:@)?([w.-]+)?:s+<(.+)>', line)
                    if match:
                        iface, parent, attrs = match.groups()
                        if 'UP' in attrs.split(','):
                            data['up'] = True
                        else:
                            data['up'] = False
                        if parent and parent in right_keys:
                            data[parent] = parent
                        continue
    
                    cols = line.split()
                    if len(cols) >= 2:
                        type_, value = tuple(cols[0:2])
    
                        iflabel = cols[-1:][0]
                        if type_ in ('inet',):
                            if 'secondary' not in cols:
                                ipaddr, netmask, broadcast = parse_network(value, cols)
                                if type_ == 'inet':
                                    if 'inet' not in data:
                                        data['inet'] = list()
                                    addr_obj = dict()
                                    addr_obj['address'] = ipaddr
                                    addr_obj['netmask'] = netmask
                                    addr_obj['broadcast'] = broadcast
                                    data['inet'].append(addr_obj)
                            else:
                                if 'secondary' not in data:
                                    data['secondary'] = list()
                                ip_, mask, brd = parse_network(value, cols)
                                data['secondary'].append({
                                    'type': type_,
                                    'address': ip_,
                                    'netmask': mask,
                                    'broadcast': brd,
                                })
                                del ip_, mask, brd
                        elif type_.startswith('link'):
                            data['hwaddr'] = value
                if iface:
                    if iface.startswith('pan') or iface.startswith('lo') or iface.startswith('v'):
                        del iface, data
                    else:
                        ret[iface] = data
                        del iface, data
            return ret
    
        def standard(self, interfaces_info):
    
            for key, value in interfaces_info.items():
                ipaddrs = set()
                netmask = set()
                if not 'inet' in value:
                    value['ipaddrs'] = ''
                    value['netmask'] = ''
                else:
                    for item in value['inet']:
                        ipaddrs.add(item['address'])
                        netmask.add(item['netmask'])
                    value['ipaddrs'] = '/'.join(ipaddrs)
                    value['netmask'] = '/'.join(netmask)
                    del value['inet']
    

    cpu.py

    #-*- coding: utf-8 -*-
    #!/usr/bin/env python3
    
    ' 获取cpu相关信息 '
    
    __author__ = 'Fwzzz'
    
    
    
    import os
    from lib.conf.config import settings
    
    class Cpu(object):
    
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/cpuinfo.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("cat /proc/cpuinfo")
            return self.parse(output)
    
        def parse(self, content):
            """
            解析shell命令返回结果
            :param content: shell 命令结果
            :return:解析后的结果
            """
            response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''}
    
            cpu_physical_set = set()
    
            content = content.strip()
            for item in content.split('
    
    '):
                for row_line in item.split('
    '):
                    key, value = row_line.split(':')
                    key = key.strip()
                    if key == 'processor':
                        response['cpu_count'] += 1
                    elif key == 'physical id':
                        cpu_physical_set.add(value)
                    elif key == 'model name':
                        if not response['cpu_model']:
                            response['cpu_model'] = value
            response['cpu_physical_count'] = len(cpu_physical_set)
    
            return response
    
  • 相关阅读:
    汉字乱码、加密后结果字符串不一致
    msgpack和TParams互相转换
    unigui监听会话开始和结束
    System.JSON.Builders.pas
    保证最终一致性的模式
    使用 Delta Sharing 协议进行数据共享
    dremio 16 升级问题
    graylog 4.0 运行
    supabase 开源firebase 可选工具
    cube.js 最新playground 说明
  • 原文地址:https://www.cnblogs.com/fwzzz/p/12460481.html
Copyright © 2020-2023  润新知