OpenLava EDA 实战(二):License 调度管理与动态资源采集

OpenLava EDA 实战(二):License 调度管理与动态资源采集

摘要:EDA 工具的 License 是昂贵且稀缺的资源,"作业启动了但 License 不够导致失败"是 EDA 集群最常见的痛点之一。本文讲解如何把 FlexLM License 数量纳入 OpenLava 的调度体系:动态采集 License 余量、配置可消耗资源、作业按 License 需求调度、避免 License 抢用冲突。同时分享 License 监控告警、统计分析的实践方案。

上一篇讲了 EDA 集群搭建和工具接入。这一篇解决 EDA 集群最核心的痛点:License 调度


零、读前必看

0.1 适合谁

  • ✅ 管理 EDA 集群,经常遇到 License 不够用的问题
  • ✅ 想让 OpenLava 自动根据 License 余量调度作业
  • ✅ 需要 License 使用率监控和统计

0.2 你将学到

主题 重点
痛点分析 为什么需要 License 调度
动态资源采集 elm 外部资源采集脚本编写
可消耗资源配置 lsf.shared / lsf.cluster 配置
作业提交 -R "rusage[vcs=1]" 调度
License 监控 使用率监控 + 告警
统计分析 License 利用率报表
高级技巧 License 队列、优先级、抢占

一、为什么需要 License 调度

1.1 痛点场景

场景:你有 5 个 VCS License,同时提交了 10 个 VCS 编译作业。

>

结果:5 个作业抢到 License 跑起来了,另外 5 个启动时报 "License checkout failed",直接失败退出。

>

浪费了调度时间、占了计算资源,作业还失败了。

1.2 理想状态

作业在排队时就知道 License 够不够,License 不足时就在队列里等,有了再调度。

作业 A(需要 1 VCS License)
   │
   ▼
OpenLava 调度器检查:
  - CPU 够不够?  ✅
  - 内存够不够?  ✅
  - VCS License 够不够?  ❌(只剩 0.5 个)
   │
   ▼
作业继续 PEND 等待
   │
   ▼
等别的作业释放了 License → 再调度运行

1.3 OpenLava 的解决方案

通过外部资源管理器(ELM / External Load Manager),定期采集 License 余量,作为可消耗资源(Consumable Resource)纳入调度。

概念 说明
ELM External Load Manager,外部负载采集器
可消耗资源 作业用了就减少,释放了就归还的资源类型
rusage 作业声明自己消耗多少资源

二、FlexLM License 信息采集

2.1 lmstat 命令

FlexLM 提供 lmstat 工具查询 License 状态:

# 查看所有 License 状态
lmstat -a -c 27000@lic_server01

# 查看特定 feature
lmstat -f VCS_MX -c 27000@lic_server01
# Users of VCS_MX:  (Total of 5 licenses issued;  Total of 2 licenses in use)
#
#   "VCS_MX" v2018.09, vendor: snpslmd, expiry: 31-dec-2025
#   vendor_string:
#   floating license
#
#     user1 workstation1 workstation1 (v2018.09) (lic_srv/27000 1234), start Wed 1/15 10:00
#     user2 workstation2 workstation2 (v2018.09) (lic_srv/27000 5678), start Wed 1/15 11:30

2.2 提取 License 余量

写个脚本解析 lmstat 输出,计算剩余 License 数量:

#!/bin/bash
# /data/eda/scripts/get_license_remain.sh
# 输出格式:resource_name value

LIC_SERVER="27000@lic_server01"
LMSTAT="/data/eda/tools/synopsys/scl/2019.06/linux64/bin/lmstat"

# 函数:提取某个 feature 的剩余数量
get_remain() {
    local feature=$1
    local output=$($LMSTAT -f "$feature" -c "$LIC_SERVER" 2>/dev/null)
    local total=$(echo "$output" | grep "Total of.*licenses issued" | grep -oP 'Total of \K\d+' | head -1)
    local in_use=$(echo "$output" | grep "Total of.*licenses in use" | grep -oP 'in use\).*Total of \K\d+' | head -1)

    if [ -z "$total" ] || [ -z "$in_use" ]; then
        echo "0"  # 查不到就返回 0,保守起见
        return
    fi

    local remain=$((total - in_use))
    echo "$remain"
}

# 输出各 License 余量
echo "vcs_license $(get_remain VCS_MX)"
echo "verdi_license $(get_remain Verdi3)"
echo "dc_license $(get_remain Design-Compiler)"
echo "simvision_license $(get_remain SimVision)"

测试一下:

chmod +x /data/eda/scripts/get_license_remain.sh
/data/eda/scripts/get_license_remain.sh
# vcs_license 3
# verdi_license 4
# dc_license 1
# simvision_license 2
每一行的格式是 **`资源名 数值`**,这是 OpenLava ELM 期望的输出格式。

三、配置 OpenLava 动态资源

3.1 第一步:在 lsf.shared 里定义资源

编辑 /opt/openlava/etc/lsf.shared

Begin   Resource
RESOURCENAME     TYPE    INTERVAL  INCREASING  CONSUMABLE  DESCRIPTION
# EDA License 资源(可消耗)
vcs_license      Numeric 60        N           Y           (VCS license count)
verdi_license    Numeric 60        N           Y           (Verdi license count)
dc_license       Numeric 60        N           Y           (Design Compiler license)
simv_license     Numeric 60        N           Y           (SimVision license count)
End     Resource

关键字段

  • TYPE = Numeric:数值型
  • INTERVAL = 60:每 60 秒采集一次
  • INCREASING = N:值越小越"好"(因为是余量,多了更好?这里要注意方向)
  • CONSUMABLE = Y:可消耗资源,作业占了就减少
**关于 INCREASING 的说明**:
- License 余量是"越多越好"的资源,所以理论上 `INCREASING = Y`
- 但对调度而言,Consumable 资源主要看"够不够",不是用于排序选择
- 实际配置中设 N 也能工作,重点是 `CONSUMABLE = Y`

3.2 第二步:配置外部资源采集器(ELM)

编辑 /opt/openlava/etc/lsf.cluster.openlava,加入 ResourceMap:

Begin   ResourceMap
RESOURCENAME     LOCATION
vcs_license      [all]
verdi_license    [all]
dc_license       [all]
simv_license     [all]
End     ResourceMap
`[all]` 表示这是集群级资源,所有节点共享,不绑定到具体节点。

3.3 第三步:配置 LIM 外部采集

lsf.conf 里配置外部采集脚本(不同版本 OpenLava 配置方式略有差异):

# /opt/openlava/etc/lsf.conf
LSF_ELM_ENABLE=Y
LSF_ELM_INTERVAL=60
LSF_ELM_SCRIPT=/data/eda/scripts/get_license_remain.sh
**注意**:不同 OpenLava 版本的 ELM 配置方式可能不同。有的版本是在 `lsf.cluster` 的 Resource 段里配 `external`,有的是独立的 `elim`(External Load Information Manager)机制。请参考你对应版本的文档。

3.4 第四步(备用方案):用 cron 周期更新

如果你的 OpenLava 版本没有标准 ELM 机制,可以用 cron + lsload / lsgrun 方式模拟:

#!/bin/bash
# /data/eda/scripts/update_license_resource.sh
# 定时采集 License 余量,更新到 OpenLava 资源

SCRIPT=/data/eda/scripts/get_license_remain.sh
OUTPUT=$($SCRIPT)

# 通过 lsadmin 命令或自定义方式更新
# 具体方式取决于你的 OpenLava 版本支持
echo "$OUTPUT" | while read resource value; do
    echo "Updating $resource = $value"
    # 方式A: 如果支持动态资源更新
    # lsadmin limcontrol ...
    # 方式B: 写入临时文件,由 LIM 读取
done

加到 crontab:

* * * * * /data/eda/scripts/update_license_resource.sh >> /var/log/license_update.log 2>&1
**说明**:OpenLava 的动态资源更新机制版本差异较大。如果官方 ELM 机制不可用,常见的替代方案是:
1. 自定义 elim 可执行文件,放在 LIM 能找到的路径
2. 用 `lsadmin reconfig` 重新加载静态配置(适合变化不频繁的)
3. 用第三方脚本 + 队列控制的方式间接管理

3.5 验证资源配置

# 1. 查看资源定义
lsinfo -r | grep -E "vcs|verdi|dc_license"

# 2. 查看资源值
lsload -l | head -20
# 应该能看到 vcs_license、verdi_license 等字段和对应的值

# 3. 确认值正确
lmstat -f VCS_MX -c 27000@lic_server01 | grep "Total of"
# 和 lsload 里看到的对比

四、按 License 调度作业

4.1 提交 VCS 作业

bsub -q normal \
  -n 4 \
  -R "rusage[mem=8192, vcs_license=1] span[hosts=1]" \
  -J vcs_compile \
  -o vcs_compile.log \
  "vcs -full64 -f filelist.f -debug_access+all -o simv -j4"

关键点-R "rusage[vcs_license=1]" —— 告诉调度器这个作业需要 1 个 VCS License。

调度器会:

  1. 看 CPU/内存够不够
  2. vcs_license 余量够不够
  3. 都够 → 调度运行
  4. 不够 → 继续 PEND 等

4.2 同时需要多个 License

# VCS 仿真 + Verdi(同时用两个 License)
bsub -n 2 \
  -R "rusage[mem=16384, vcs_license=1, verdi_license=1]" \
  -o sim.log \
  "./simv +fsdb=wave.fsdb +UVM_VERBOSITY=MEDIUM"

4.3 查看 PEND 原因

bjobs -p <jobid>
# Job is waiting for the following resources:
#    1 host with vcs_license>=1

看到 vcs_license>=1,说明作业在等 License,而不是等 CPU/内存。

4.4 队列配置优化

建议按 License 类型划分队列,更精细地控制:

Begin   Queue
QUEUE_NAME     = vcs_normal
PRIORITY       = 30
RESOURCE_REQ   = rusage[vcs_license=1]
QJOB_LIMIT     = 20
HOSTS          = all
USERS          = all
End     Queue

Begin   Queue
QUEUE_NAME     = dc_synthesis
PRIORITY       = 40
RESOURCE_REQ   = rusage[dc_license=1]
QJOB_LIMIT     = 5
HOSTS          = all
USERS          = dc_team
End     Queue

好处

  • 不同工具的作业互不干扰
  • 可以针对每种 License 设置不同的队列上限
  • 用户体验更好(提交到对应的队列就行)

五、License 监控告警

5.1 使用率监控

#!/bin/bash
# /data/eda/scripts/check_license_usage.sh
# 检查各 License 使用率,超过阈值告警

LIC_SERVER="27000@lic_server01"
LMSTAT="/data/eda/tools/synopsys/scl/2019.06/linux64/bin/lmstat"
THRESHOLD=80    # 超过 80% 告警

check_feature() {
    local feature=$1
    local output=$($LMSTAT -f "$feature" -c "$LIC_SERVER" 2>/dev/null)
    local total=$(echo "$output" | grep "licenses issued" | grep -oP 'Total of \K\d+' | head -1)
    local in_use=$(echo "$output" | grep "licenses in use" | grep -oP 'in use\).*Total of \K\d+' | head -1)

    if [ -z "$total" ] || [ "$total" -eq 0 ]; then
        return
    fi

    local usage=$((in_use * 100 / total))
    echo "$feature: $in_use/$total (${usage}%)"

    if [ "$usage" -ge "$THRESHOLD" ]; then
        echo "WARNING: $feature 使用率超过 ${THRESHOLD}%!"
        # 这里可以接邮件/钉钉/企业微信告警
    fi
}

for feature in VCS_MX Verdi3 Design-Compiler SimVision; do
    check_feature "$feature"
done

5.2 Prometheus 监控(推荐)

如果你有 Prometheus,写个 exporter 更专业:

#!/usr/bin/env python3
"""
FlexLM License Exporter for Prometheus
"""
import subprocess
import re
from prometheus_client import Gauge, start_http_server
import time

LICENSE_SERVERS = ["27000@lic_server01"]
FEATURES = ["VCS_MX", "Verdi3", "Design-Compiler"]
LMSTAT = "/data/eda/tools/synopsys/scl/2019.06/linux64/bin/lmstat"

# 定义指标
license_total = Gauge('eda_license_total', 'Total licenses', ['feature', 'vendor'])
license_used = Gauge('eda_license_used', 'Used licenses', ['feature', 'vendor'])
license_available = Gauge('eda_license_available', 'Available licenses', ['feature', 'vendor'])

def collect_licenses():
    for server in LICENSE_SERVERS:
        for feature in FEATURES:
            try:
                result = subprocess.run(
                    [LMSTAT, '-f', feature, '-c', server],
                    capture_output=True, text=True, timeout=10
                )
                output = result.stdout

                # 提取总数
                total_match = re.search(r'Total of (\d+) licenses issued', output)
                used_match = re.search(r'Total of (\d+) licenses in use', output)

                if total_match and used_match:
                    total = int(total_match.group(1))
                    used = int(used_match.group(1))
                    avail = total - used

                    license_total.labels(feature=feature, vendor='synopsys').set(total)
                    license_used.labels(feature=feature, vendor='synopsys').set(used)
                    license_available.labels(feature=feature, vendor='synopsys').set(avail)
            except Exception as e:
                print(f"Error collecting {feature}: {e}")

if __name__ == '__main__':
    start_http_server(9100)  # 监听 9100 端口
    while True:
        collect_licenses()
        time.sleep(60)

5.3 告警规则(Prometheus)

groups:
  - name: license_alerts
    rules:
      - alert: LicenseUsageHigh
        expr: eda_license_used / eda_license_total > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "License 使用率 > 90%"
          description: "{{ $labels.feature }} 使用率 {{ $value | humanizePercentage }}"

      - alert: LicenseServerDown
        expr: eda_license_total == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "License 服务器不可达"
          description: "{{ $labels.feature }} 查不到 License 总数"

      - alert: LicenseExpiringSoon
        expr: days_until_license_expiry < 30
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "License 即将过期(<30天)"

六、License 使用统计

6.1 历史使用率统计

#!/bin/bash
# /data/eda/scripts/license_daily_report.sh
# 每日 License 使用统计

LOG_DIR=/data/eda/license_logs
DATE=$(date +%Y%m%d)

# 每 5 分钟采样一次,写入日志
# 可以用 awk 分析峰值、平均值

echo "=== License 日报 $(date +%Y-%m-%d) ==="
echo

for feature in VCS_MX Verdi3 Design-Compiler; do
    echo "--- $feature ---"
    # 从采样日志里统计
    if [ -f "$LOG_DIR/${feature}_${DATE}.log" ]; then
        echo "峰值: $(awk '{print $2}' "$LOG_DIR/${feature}_${DATE}.log" | sort -rn | head -1)"
        echo "平均: $(awk '{sum+=$2; n++} END {printf "%.1f", sum/n}' "$LOG_DIR/${feature}_${DATE}.log")"
    fi
done

6.2 月度趋势分析

把每天的数据存下来,月底生成趋势图:

import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

# 读取一个月的采样数据
df = pd.read_csv('license_usage_202401.csv')
df['time'] = pd.to_datetime(df['time'])
df = df.set_index('time')

# 按小时取最大值
hourly_max = df.resample('H').max()

# 画趋势图
plt.figure(figsize=(12, 6))
for col in ['vcs_license', 'verdi_license']:
    plt.plot(hourly_max.index, hourly_max[col], label=col)
plt.title('Monthly License Usage (Hourly Peak)')
plt.legend()
plt.savefig('license_monthly_trend.png')

用途

  • 评估 License 是否够用
  • 决定要不要加买 License
  • 做预算分析

七、高级技巧

7.1 License 队列分级

Begin   Queue
QUEUE_NAME     = vcs_priority
PRIORITY       = 50
RESOURCE_REQ   = rusage[vcs_license=1]
QJOB_LIMIT     = 3
USERS          = lead_engineer project_manager
PREEMPTION     = PREEMPTIVE
End     Queue

Begin   Queue
QUEUE_NAME     = vcs_normal
PRIORITY       = 30
RESOURCE_REQ   = rusage[vcs_license=1]
QJOB_LIMIT     = 5
USERS          = all
PREEMPTION     = PREEMPTABLE
End     Queue

逻辑

  • 高优先级队列的作业来了,可以抢占低优先级队列的 License
  • 用于"紧急任务插队"场景
**慎用抢占**。被抢占的作业会失败退出,需要配合 checkpoint 或自动重跑。

7.2 License + FairShare

结合 FairShare,保证不同团队公平使用 License:

Begin   Queue
QUEUE_NAME     = vcs_shared
PRIORITY       = 30
RESOURCE_REQ   = rusage[vcs_license=1]
FAIRSHARE      = USER_GROUP_SHARES[
    [team_A, 40, [default, 1]]
    [team_B, 30, [default, 1]]
    [team_C, 30, [default, 1]]
]
End     Queue

三个团队按 4:3:3 的比例共享 10 个 VCS License,不会被一个团队占满。

7.3 避免 License 泄漏

License 泄漏:作业被杀掉了,但 License 没及时归还,被 FlexLM 认为还在使用。

原因

  • 作业被 bkill -r 强制杀掉,进程来不及释放 License
  • 节点宕机

解决方案

# 1. 用 epilog 脚本,作业结束后确认 License 释放
# /data/eda/scripts/epilog_license_check.sh
# (具体取决于工具,有些需要手动 lmremove)

# 2. 配置 FlexLM 的 TIMEOUT(长作业不建议设太短)
# 在 License 文件里加:
# OPTIONS= /data/eda/license/synopsys.opt
# 在 synopsys.opt 里:
# TIMEOUT VCS_MX 3600

# 3. 定期检查 + 手动释放
# lmremove -c 27000@lic_server01 VCS_MX user1 host1 display1

7.4 License 借用(roaming)

用户带回家办公用,可借用 License。但要注意借用数量影响集群可用数量。

八、常见问题

Q1:License 采集脚本不执行?

检查:

  1. 脚本有执行权限:chmod +x
  2. LIM 用户(lsf)能执行脚本
  3. 脚本路径在 PATH 里或者写绝对路径
  4. lim.log 有没有报错

Q2:作业调度了但还是 License 失败?

可能原因:

  1. 采集延迟:采集是周期性的(比如 60 秒),调度时刻看到有余量,但实际启动时已经被别的作业抢了
  2. 调度器和真实 License 不同步:外部采集有延迟
  3. License 被集群外用户占用:比如有人在自己工作站上用

缓解措施

  • 缩短采集间隔(但别太短,增加 License Server 负担)
  • 保守估计(比如配置时少算 1-2 个做缓冲)
  • 对作业加启动重试机制

Q3:怎么统计每个用户用了多少 License?

# 实时看
lmstat -f VCS_MX -c 27000@lic_server01

# 历史统计
# 用采样日志,按用户聚合

也可以用专业的 License 管理工具(如 FlexNet Manager、Reprise RLM)。

九、写在最后

License 调度是 EDA 集群的核心竞争力——同样 5 个 VCS License,调度得好团队效率翻倍,调度不好天天有人喊"License 不够"。

核心思路就三点:

  1. 采得到:把 License 余量动态采集到 OpenLava
  2. 算得准:作业声明自己消耗多少 License,调度器据此调度
  3. 看得见:监控 + 统计 + 告警,掌握 License 使用情况

下一篇我们讲 VCS 仿真最佳实践:怎么组织 VCS 编译和仿真作业、并行编译、波形管理、调试技巧、回归测试等。


你们团队 License 够用吗?最紧缺的是哪个工具? 评论区聊聊。

OpenLava #EDA #License #FlexLM #VCS #调度 #运维 #监控

发表回复

后才能评论