数据库表设计

  1. user 表:
列名 数据类型 说明
id INTEGER PRIMARY KEY 主键
username TEXT 用户名
  1. server 表:
列名 数据类型 说明
id INTEGER PRIMARY KEY 主键
hostname TEXT 主机名
  1. vendor 表:
列名 数据类型 说明
id INTEGER PRIMARY KEY 主键
vendor_daemon_name TEXT 供应商守护进程名
  1. license_type 表:
列名 数据类型 说明
id INTEGER PRIMARY KEY 主键
type TEXT 许可类型
  1. feature 表:
列名 数据类型 说明
id INTEGER PRIMARY KEY 主键
feature_name TEXT 特性名称
  1. license_usage_log 表:
列名 数据类型 说明
id INTEGER PRIMARY KEY 主键
user_id INTEGER 用户ID,外键
workstation_id INTEGER 工作站ID,外键
sessionid INTEGER sessionid,从日志条目解析出来的
start_time TEXT 开始时间
end_time TEXT 结束时间
vendor_id INTEGER vendor ID,外键
lic_server_id INTEGER license server ID,外键
lic_type_id INTEGER license_type ID,外键
feature_id INTEGER feature ID,外键
additional_key TEXT 部分feature有额外的key

每个表格通过主键和外键相互关联,为日志解析和数据存储提供结构支持。

初始化数据库

#!/Users/wanlinwang/spack/opt/spack/darwin-ventura-m1/apple-clang-14.0.3/python-3.10.8-b7rgkczw4yfgrkbgttbcbur3ksmwho5d/bin/python3


import sqlite3

conn = sqlite3.connect('license_logging.db')  # 创建或连接到license.db数据库
cursor = conn.cursor()

# 创建user表
cursor.execute('''
CREATE TABLE IF NOT EXISTS user (
    id INTEGER PRIMARY KEY,
    username TEXT
)
''')

# 创建server表
cursor.execute('''
CREATE TABLE IF NOT EXISTS server (
    id INTEGER PRIMARY KEY,
    hostname TEXT
)
''')

# 创建vendor表
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendor (
    id INTEGER PRIMARY KEY,
    vendor_daemon_name TEXT
)
''')

# 创建license_type表
cursor.execute('''
CREATE TABLE IF NOT EXISTS license_type (
    id INTEGER PRIMARY KEY,
    type TEXT
)
''')

# 创建feature表
cursor.execute('''
CREATE TABLE IF NOT EXISTS feature (
    id INTEGER PRIMARY KEY,
    feature_name TEXT
)
''')

# 创建license_usage_log表,它将其他表关联起来
cursor.execute('''
CREATE TABLE IF NOT EXISTS license_usage_log(
    id INTEGER PRIMARY KEY,
    user_id INTEGER,
    workstation_id INTEGER,
    sessionid INTEGER,
    start_time TEXT,
    end_time TEXT,
    vendor_id INTEGER,
    lic_server_id INTEGER,
    lic_type_id INTEGER,
    feature_id INTEGER,
    additional_key TEXT,
    FOREIGN KEY(user_id) REFERENCES user(id),
    FOREIGN KEY(workstation_id) REFERENCES server(id),
    FOREIGN KEY(vendor_id) REFERENCES vendor(id),
    FOREIGN KEY(lic_server_id) REFERENCES server(id),
    FOREIGN KEY(lic_type_id) REFERENCES license_type(id),
    FOREIGN KEY(feature_id) REFERENCES feature(id)
)
''')

conn.commit()  # 提交创建表的操作
conn.close()  # 关闭数据库连接

定期执行,存入/更新数据库

#!/Users/wanlinwang/spack/opt/spack/darwin-ventura-m1/apple-clang-14.0.3/python-3.10.8-b7rgkczw4yfgrkbgttbcbur3ksmwho5d/bin/python3


import sqlite3
import subprocess
import re
from datetime import datetime

DB_PATH = 'license_logging.db'

def run_lmstat(CDS_LIC_FILE):
    cmd = f"lmstat -a -c {CDS_LIC_FILE}"
    return subprocess.check_output(cmd, shell=True).decode('utf-8')

def extract_data_from_output(output):
    features_data = []
    lines = output.split('\n')
    current_feature = None
    for line in lines:
        if 'Users of' in line:
            # Get the feature name
            current_feature = line.split()[2].strip(':')
            lic_type = "unknown"
        elif current_feature and current_feature in line:
            vendor_info = re.search(r'.*vendor:\s*(\w+).*', line)
            if vendor_info:
                current_vendor = vendor_info.group(1)
        elif "floating license" in line:
            lic_type = "floating license"
        elif current_feature and ', start' in line:
            # 格式"user_01 y260.ic.cn y260.ic.cn:1.0 Xcelium Single Core Engine (v21.000) (y162/5280 801), start Thu 10/19 9:57"
            user_info = re.search(r'(\w+)\s+([\w.]+)\s+[\w.]*:[\d.]+\s+[^(]*\(([\w.]+)\)\s+\(([\w]+)/(\d+)\s+(\d+)\),\s*start\s*(\w+ \d+/\d+ \d+:\d+)', line)
            if user_info:
                start_time = datetime.strptime(user_info.group(7), "%a %m/%d %H:%M").replace(year=datetime.now().year)
                if start_time > datetime.now(): start_time = start_time.replace(year=start_time.year - 1)

                features_data.append({
                    "feature": current_feature,
                    "vendor": current_vendor,
                    "lic_type": lic_type,
                    "username": user_info.group(1),
                    "workstation": user_info.group(2),
                    "version": user_info.group(3),
                    "lic_server": user_info.group(4),
                    "port": user_info.group(5),
                    "sessionid": user_info.group(6),
                    "start_time": start_time.strftime("%Y-%m-%d %H:%M:%S")
                })
    return features_data

def get_or_insert_id(cursor, table, column, value):
    cursor.execute(f'SELECT id FROM {table} WHERE {column}=?', (value,))
    result = cursor.fetchone()
    if not result:
        cursor.execute(f'INSERT INTO {table}({column}) VALUES (?)', (value,))
        return cursor.lastrowid
    return result[0]

def update_database(data):
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()
    
    for entry in data:
        # Resolve foreign key dependencies
        user_id = get_or_insert_id(cursor, 'user', 'username', entry['username'])
        feature_id = get_or_insert_id(cursor, 'feature', 'feature_name', entry['feature'])
        vendor_id = get_or_insert_id(cursor, 'vendor', 'vendor_daemon_name', entry['vendor'])
        lic_type_id = get_or_insert_id(cursor, 'license_type', 'type', entry['lic_type'])
        workstation_id = get_or_insert_id(cursor, 'server', 'hostname', entry['workstation'])
        lic_server_id = get_or_insert_id(cursor, 'server', 'hostname', entry['lic_server'])

        # Check if entry exists in license_usage_log
        cursor.execute('''SELECT id FROM license_usage_log WHERE feature_id=? AND start_time=? AND sessionid=?''',
                       (feature_id, entry["start_time"], entry["sessionid"]))
        log_id = cursor.fetchone()
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if log_id:
            # Update end_time
            cursor.execute('UPDATE license_usage_log SET end_time=? WHERE id=?', 
                           (timestamp, log_id[0]))
        else:
            # Insert new entry
            cursor.execute('''
                INSERT INTO license_usage_log(user_id, workstation_id, start_time, end_time, vendor_id, lic_server_id, lic_type_id, feature_id, sessionid) 
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)''', 
                (user_id, workstation_id, entry["start_time"], timestamp, vendor_id, lic_server_id, lic_type_id, feature_id, entry["sessionid"]))

    conn.commit()
    conn.close()

if __name__ == "__main__":
    CDS_LIC_FILE="5280@lic-server-01:5280@lic-server-02"
    all_output = run_lmstat(CDS_LIC_FILE)
    for output in re.split(r"-{8,}", all_output):
        data = extract_data_from_output(output)
        update_database(data)

测试数据

    output = """Users of Affirma_sim_analysis_env:  (Total of 8 licenses issued;  Total of 3 licenses in use)

  "Affirma_sim_analysis_env" v23.0, vendor: cdslmd, expiry: 15-jan-2024
  vendor_string: UHD:PERM
  floating license

    user_01 TENCENT64.site :11 (v16.000) (lic460/5280 101), start Wed 10/18 12:37
    user_02 server-0431840.ic.cn server-0431840.ic.cn:1.0 (v16.000) (lic460/5280 716), start Wed 10/18 17:24
    user_03 server-2381620.ic.cn server-2381620.ic.cn:4.0 (v16.000) (lic460/5280 2123), start Thu 10/19 6:56

Users of Xcelium_Single_Core:  (Total of 8 licenses issued;  Total of 3 licenses in use)

  "Xcelium_Single_Core" v21.000, vendor: cdslmd, expiry: 15-jan-2024
  vendor_string: UHD:PERM
  floating license

    user_03 TENCENT64.site :11 Xcelium Single Core Engine (v21.000) (lic460/5280 101), start Wed 10/18 12:37
    user_02 TENCENT64.site :4 Xcelium Single Core Engine (v21.000) (lic460/5280 301), start Wed 10/18 12:37
    user_01 TENCENT64.site :80 Xcelium Single Core Engine (v21.000) (lic460/5280 401), start Wed 10/18 12:37"""