LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

Linux下功耗测试开发初探

1、前言

Linux嵌入式设备开发,对于功耗要求较高。在遇到问题的时候,定位手段有限。

关于功耗一种是静态功耗,指的是某一种状态下的功耗情况,在这种状态下功耗尽量的低,比如suspended底电流越低越好、cpuidle的深睡和浅睡、cpu unplug等;

另一种是动态功耗,指的是各种状态的切换,比如susped-resume流程、cpuidle不同状态切换驻留时间、中断导致的唤醒频繁程度、wakelock导致的系统唤醒时长、在完成相同工作情况下cpufreq是否尽量低等等。

 

静态功耗的调试,可以在特定状态下测量功耗,逐步优化;

但是在动态功耗,也即系统运行时,影响功耗的因素千差万别,瞬息万变,就需要在测量功耗的时候抓住系统运行轨迹。

 

下面主要关注如何对功耗异常点进行定位,更快的发现异常点的原因。

2、内核功耗测试锚点

要测量功耗因素,就要了解那些内核行为导致了功耗增加或减少?具体需要抓住哪些轨迹,比如:

1. suspend-resume(何时进入suspend、持续时间、何时退出suspend、谁唤醒了系统):machine_suspend----------成对出现

2. cpuidle(切换点、时长、状态):cpuidle-----------------------------------------------------------成对出现

3. cpufreq(何时切换、切换到什么频点、持续多久):cpu_frequency----------------------------------------单点

4. 中断(哪个中断、何时触发、中断函数处理耗时):irq_handler_entry、irq_handler_exit--------------------成对出现

5. wakeup source():wakeup_source_activate、wakeup_source_deactivate---------------------------成对出现

6. 电源域(哪个电源域、设置状态):power_domain_target-----------------------------------------------成对出现

7. 时钟(哪个时钟、何时开、何时关、频率变动):clock_enable、clock_disable、clock_set_rate--------------成对出现

 

其他轨迹:

8. 定时器(定时器创建、启动、超时、取消,包括低精度定时器和高精度定时器):
8.1 低精度定时器(timer_init、timer_start、timer_cancel、timer_expire_entry、timer_expire_exit)-----------------成对出现
8.2 高精度定时器(hrtimer_init、hrtimer_start、hrtimer_expire_entry、hrtimer_expire_exit、hrtimer_cancel)-------成对出现
9. ...

 

可以把这些一个个Traceevents看做功耗信息锚点,下面逐个分析这些锚点。

除了每个锚点消息,一些公共信息比如:cpu id、pid、flags需要保留,以便分析。

flags:rqs-off、need-resched、hardirq/softirq、preempt-depth。

 

# entries-in-buffer/entries-written: 12876/12876   #P:1
#
#                              _-----=> irqs-off-------------------'d'表示中断被disabled,'.'表示中断没有被关闭。
#                             / _----=> need-resched---------------'N'表示need_resched被设置,'.'表示need-resched没有被设置,中断返回不会进行进程切换
#                            | / _---=> hardirq/softirq------------'H'在softirq中发生了硬件中断,'h'表示硬中断,'s'表示软中断,'.'不在中断上下文中,普通状态。
#                            || / _--=> preempt-depth---------------当抢占中断使能后,该域代表preempt_disabled的级别。
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
           <...>-1323  [000] d.h1    20.374237: irq_handler_entry: irq=41 name=zx29_i2c.0
           <...>-1323  [000] dNh1    20.374237: irq_handler_exit: irq=41 ret=handled
   zte_usbCfgMng-760   [000] d...    20.374268: wakeup_source_deactivate: zx29-i2c state=0x284b0005
   zte_usbCfgMng-760   [000] d...    20.374268: wakeup_source_activate: zx29-i2c state=0x284b0006
           <...>-1327  [000] d.h.    20.374329: irq_handler_entry: irq=41 name=zx29_i2c.0
           <...>-1327  [000] dNh.    20.374359: irq_handler_exit: irq=41 ret=handled
           <...>-1323  [000] d.h.    20.374420: irq_handler_entry: irq=41 name=zx29_i2c.0
           <...>-1323  [000] dNh.    20.374420: irq_handler_exit: irq=41 ret=handled
   zte_usbCfgMng-760   [000] d...    20.374451: wakeup_source_deactivate: zx29-i2c state=0x284c0005
   zte_usbCfgMng-760   [000] d...    20.374451: wakeup_source_activate: zx29-i2c state=0x284c0006
           <...>-1323  [000] dNh.    20.374573: irq_handler_entry: irq=41 name=zx29_i2c.0
           <...>-1323  [000] dNh.    20.374573: irq_handler_exit: irq=41 ret=handled

按照对功耗影响大小来排序,顺序应该是:suspend/resume最省电、其次是cpu hotplug、在某些情况下cpuidle也能达到cpu hotplug,再然后就是cpufreq的动态调压调频以及各种设备的RPM。

其他的情况还包括wakelock和中断,这些具体影响多少依情况变化很大。

2.1 trace_machine_suspend

suspend/resume的锚点位于suspend_devices_and_enter中:

int suspend_devices_and_enter(suspend_state_t state)
{
...
    trace_machine_suspend(state);
...
    trace_machine_suspend(PWR_EVENT_EXIT);
...
}

 

在suspend_ops->begin之前输出进入何种状态的suspend,在suspend_ops->end之后输出退出suspend标识。

kworker/u:2-623   [000] ....   164.728912: machine_suspend: state=3-----------------------对应MEM类型的suspend
kworker/u:2-623 [000] .N.. 309.235962: machine_suspend: state=4294967295--------------PWR_EVENT_EXIT的值,也即-1
kworker/u:2-623 [000] .... 310.279083: machine_suspend: state=3
kworker/u:2-623 [000] .N.. 351.880157: machine_suspend: state=4294967295

#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 1)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)

这两个肯定是成对出现,并且他们之间的时间段就是系统睡眠时间。他们之外的就是系统运行的时间。

2.2 trace_cpu_idle_rcuidle

cpuidle的Governor制定cpuidle状态切换策略。

cpuidle锚点位于cpuidle_idle_call中:

int cpuidle_idle_call(void)
{
...
    trace_power_start_rcuidle(POWER_CSTATE, next_state, dev->cpu);
    trace_cpu_idle_rcuidle(next_state, dev->cpu);-----------------------即将进入cpuidle

    if (cpuidle_state_is_coupled(dev, drv, next_state))
        entered_state = cpuidle_enter_state_coupled(dev, drv,
                                next_state);
    else
        entered_state = cpuidle_enter_state(dev, drv, next_state);

    trace_power_end_rcuidle(dev->cpu);
    trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, dev->cpu);------------------退出cpuidle
...
}

 进入的时候记录即将进入的状态,退出的时候也是-1这个特殊值:

          <idle>-0     [000] d...    17.718323: cpu_idle: state=0 cpu_id=0
          <idle>-0     [000] ....    17.718506: cpu_idle: state=4294967295 cpu_id=0
          <idle>-0     [000] d...    17.718506: cpu_idle: state=0 cpu_id=0
          <idle>-0     [000] ....    17.719177: cpu_idle: state=4294967295 cpu_id=0

 

cpuidle有两种状态:OFF深睡和WFI浅睡,这两种需要区分,因为深睡和浅睡对于功耗的影响还是比较大的。

2.3 trace_cpu_frequency

cpufreq和cpuidle之类的trace不一样,cpufreq只需要记录切换频率的点,而不是时间段。

cpufreq的Governor制定侧率,具体的频率点有OPP定义。

void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state)
{
...
    switch (state) {

    case CPUFREQ_PRECHANGE:
...
    case CPUFREQ_POSTCHANGE:
...
        trace_cpu_frequency(freqs->new, freqs->cpu);---------------在频率切换后接收消息。
...
    }
}

static int xxx_target(struct cpufreq_policy *policy,
               unsigned int target_freq,
               unsigned int relation)
{
...
    for_each_online_cpu(cpu)
    {
        freqs.cpu = cpu;
        cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
    }
...----------------------------------------------------------------------------执行频率切换的主题
    for_each_online_cpu(cpu)
    {
        freqs.cpu = cpu;
        cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);-----------------因为频率切换是每个芯片特有的,但是每次切换之后都会发送CPUFREQ_POSTCHANGE。锚点就在此消息处理点中。
    }
...    
}

 

记录频率切换值和cpu序号:

    Line 11743:      kworker/u:1-8     [000] ....    61.859894: cpu_frequency: state=624000 cpu_id=0
    Line 11824:      kworker/u:1-8     [000] ....   309.365387: cpu_frequency: state=312000 cpu_id=0
    Line 12173:      kworker/u:1-8     [000] ....   310.324127: cpu_frequency: state=624000 cpu_id=0
    Line 12191:      kworker/u:1-8     [000] ....   950.204895: cpu_frequency: state=312000 cpu_id=0

 

2.4 trace_irq_handler_entry/trace_irq_handler_exit

中断的锚点放在中断例程前后,可以统计中断执行时间、中断频率等。

void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc)
{
...
    trace_irq_handler_entry(irq, action);
    res = action->handler(irq, dev_id);
    trace_irq_handler_exit(irq, action, res);
...
}

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int flags = 0, irq = desc->irq_data.irq;

    do {
...
        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, res);
...
}

 

在中断例程之前记录中断号和中断名称,在之后记录中断号和执行结果:

          <idle>-0     [000] d.h.   950.700256: irq_handler_entry: irq=68 name=dwc_otg_pcd
          <idle>-0     [000] d.h.   950.700317: irq_handler_exit: irq=68 ret=handled

 

基于此,可以统计中断频繁程度、中断例程执行时间,如果中断例程执行时间均方差较大,说明中断例程不太稳定。

2.5 trace_wakeup_source_activate/trace_wakeup_source_deactivate

wakeup source分为两大类:超时wakeup source和手动开关。超时wakeup souce会自动关闭,最多超时时间不准。但是手段lock/unlock的wakeup source,存在不正确关闭的情况。

static void wakeup_source_activate(struct wakeup_source *ws)
{
...
    trace_wakeup_source_activate(ws->name, cec);
...
}

static void wakeup_source_deactivate(struct wakeup_source *ws)
{
...
    trace_wakeup_source_deactivate(ws->name, cec);
...
}

 

wakeup source和cpuidle、suspend/resume不一样支出在于,不同的wakeup source时间上可能存在重叠。

一个wakeup source的生命周期从wakeup_source_activate开始,到wakeup_source_deactivate结束。

[000] d... 20.269409: wakeup_source_activate: usbcfgmng_lock state=0x28280004--------------锁
...
[000] d... 20.394073: wakeup_source_deactivate: usbcfgmng_lock state=0x28520004------------释放锁

 

通过分析,可以知道那些wakeup source未能正常释放,那些wakeup source导致系统lock长时间。

2.6 trace_power_domain_target

2.7 trace_clock_enable/trace_clock_disable

2.8 trace_timer_start/trace_timer_cancel/trace_timer_expire_entry/trace_timer_expire_exit

在一个timer起来之后,它的生命存在两种情况:被取消和超时。内核中的timer分为两大类:低精度timer和hrtimer。

hrtimer太过频繁,Trace量会非常大。所以看一下低精度timer。

static inline void
debug_activate(struct timer_list *timer, unsigned long expires)
{
    debug_timer_activate(timer);
    trace_timer_start(timer, expires);--------------------------------启动timer
}

static inline void debug_deactivate(struct timer_list *timer)
{
    debug_timer_deactivate(timer);
    trace_timer_cancel(timer);---------------------------------------取消timer
}

static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long),
              unsigned long data)
{
...
    trace_timer_expire_entry(timer);---------------------------------timer超时函数入口
    fn(data);
    trace_timer_expire_exit(timer);----------------------------------timer超时函数退出
...
}

关于timer的统计信息,还可以从/proc/timer_stats中读取。默认状态为关闭,使能通过echo 1 > /proc/timer_stats。

 

3. 抓取Tracepoint数据

抓取Tracepoint需要有针对性,不然数据量太大,导致冗余信息太多,而且buffer空间有限,就会导致buffer不够用。

针对Tracepoint的抓取粒度可以很细:一是针对Tracepoint events进行开关,二是在Tracepoint events内部还可以使用filter。

3.1 抓取Trace设置

#Turn off tracing
echo 0 >/sys/kernel/debug/tracing/tracing_on------------------------------先停止Trace抓取
#Disable all traceevents
echo 0 >/sys/kernel/debug/tracing/events/enable---------------------------关闭所有Trace events
#Clear trace buffer
echo > /sys/kernel/debug/tracing/trace------------------------------------清空Trace buffer

#Enable power related trace events
echo 1 >/sys/kernel/debug/tracing/events/xxx/xxx/enable-------------------打开具体的Trace events,第一层过滤

#Set tracing buffer size
echo 4096 >/sys/kernel/debug/tracing/buffer_size_kb-----------------------设置Trace ring buffer大小,可根据情况动态调节

#Move filter function to filter.sh for temp-------------------------------设置每个Trace event的过滤,更细节,需要参考内核代码进行设置。
echo 0 >/sys/kernel/debug/tracing/events/power/wakeup_source_activate/filter

echo 0 >/sys/kernel/debug/tracing/events/power/wakeup_source_deactivate/filter

echo \"((name!=xxx))\" > /sys/kernel/debug/tracing/events/power/wakeup_source_activate/filter

echo \"((name!=xxx))\" > /sys/kernel/debug/tracing/events/power/wakeup_source_deactivate/filter

#Turn on tracing echo 1 >/sys/kernel/debug/tracing/tracing_on-----------------------------开始抓取Trace #Duration of capture trace sleep(50)----------------------------------------------------------------特定时间或采取其他措施 #Disable all traceevents echo 0 >/sys/kernel/debug/tracing/events/enable--------------------------关闭Trace抓取 #Save trace evnts cat /sys/kernel/debug/tracing/trace > /trace.txt-------------------------将Trace内容保存到文件中

还可以通过cat trace_pipe > trace.txt来抓取trace,这种情况抓取trace后会将抓取的内容清除。

就不用担心由于RingBuffer溢出导致的Trace丢失问题。

 

3.2 Trace event的过滤功能filter

对于Trace events的过滤通过写入字符串给对应events的filter中,如:

echo \"((name!=xxx))\" > /sys/kernel/debug/tracing/events/power/wakeup_source_activate/filter

将会过滤掉名称等于xxx的wakeup_source_activate。name是所谓的Field,这需要根据event不同在代码中找到。

比较符号还可以使用:==、>=、<=、!=、<、>,逻辑符号可以是||、&&。

由于wakeup source和irq两种类型events的trace特别多,看看这两种filter信息。

3.2.1 wakeup source filter

wakeup_source_activate和wakeup_source_deactivate都可以通过name和state进行设置filter。

name: wakeup_source_activate
ID: 74
format:
...
        field:__data_loc char[] name;   offset:12;      size:4; signed:0;
        field:u64 state;        offset:16;      size:8; signed:0;

print fmt: "%s state=0x%lx", __get_str(name), (unsigned long)REC->state

ID: 73
format:
...
        field:__data_loc char[] name;   offset:12;      size:4; signed:0;
        field:u64 state;        offset:16;      size:8; signed:0;

print fmt: "%s state=0x%lx", __get_str(name), (unsigned long)REC->state

 

3.2.2 irq filter

name: irq_handler_entry
ID: 22
format:
...
        field:int irq;  offset:12;      size:4; signed:1;
        field:__data_loc char[] name;   offset:16;      size:4; signed:0;

print fmt: "irq=%d name=%s", REC->irq, __get_str(name)

name: irq_handler_exit
ID: 21
format:
...
        field:int irq;  offset:12;      size:4; signed:1;
        field:int ret;  offset:16;      size:4; signed:1;

print fmt: "irq=%d ret=%s", REC->irq, REC->ret ? "handled" : "unhandled"

 

4. 抓取功耗数据

通过GPIB-USB连接Keisight 66319D,使用PyVISA抓取功耗数据。

Windwos下需要安装《Agilent IO Libraries Suite》,可以到Keisight IO程序库套件下载。

安装好后可以在设备管理器中看到对应的设备,GPIB0::5::INSTR对应GPIB编号0,设备号对应5。

Keisight 66319D使用

对66319D的编程需要参考《User's Guide Agilent Model 66111A ... Mobile Communications DC Source》,重点在《Chapter 7 Programming the DC Source》。

1. Programming the Output

当仪表被上电后,输出是被关闭的,电压输出为0。系统在上电是默认执行了以下命令:

*RST 
*CLS 
STAT:PRES 
*SRE 0 
*ESE 0

2. 设置输出电流电压值

VOLT  5---------设置输出1电压为5V
VOLT2 5---------设置输出2电压为5V

CURR  <n>-------设置输出1电流为nA
CURR2 <n>-------设置输出2电流为nA

OUTP OFF--------关闭输出
OUTP ON---------打开输出

 

3. 读取电流电压值

MEAS:VOLT?
MEAS:CURR?--------------------------测试输出1的电压电流值,这个值是一个平均值,仪表内部已经做过运算。他受到采样周期、采样数、运算窗口函数影响。

MEAS:VOLT2?
MEAS:CURR2?-------------------------测试输出2的电压电流值

SENS:SWE:TINT <sample_period>-------采样周期
SENS:SWE:POIN <points>--------------采样数
SENS:SWE:TINT 46.8E-6;POIN 1500-----采样周期和采样数二合一
SENS:WIND: HANN | RECT--------------设置仪表内部测量窗口函数,汉宁窗口或矩形窗口

 

在仪表上电后有一个默认状态,电压电流的采样率是15.6微秒,窗口扫描尺寸是2048个。15.6X2048即32毫秒左右,加上命令处理需要额外10+毫秒左右,所以实际读取周期45ms左右是正常的。

如何提高读取周期呢?不能简单的通过设置采样周期,降低采样数来是想。

尽量保持最低采样周期,调整采样数来观察数据,如果采样数过低会导致电流负值出现。

实测在采样数为64时,电流比较正常稳定。

 

具体抓取的在5.4 PyVISA中介绍。

5. 工具基础库

可能遇到的集中类型锚点:

1.电流功耗图形,X轴timeline从0s开始,Y轴是mA数据。其他所有锚点数据X轴timeline严格和电流功耗0s同步。

2.只有一个点时间的cpufreq,绘制成不同高度的阶梯图形。

3.有起止点时间的cpuidle/timer/wakesource/resume-suspend等,绘制成条状。

4.对每个内核行为的功耗进行量化,比如runing多少功耗、cpuidle深睡多少功耗、cpuidle多少功耗、不同cpufreq多少功耗,然后建立模型绘出预期值。和实际功耗图对比,找出哪里异常。

5.1 matplotlib

matplotlib.pyplot.step

matplotlib.pyplot.broken_barh

 

5.2 csv

CSV即Comma Separated Values,是一种简单的数据存取文件。其使用也很简单。

CSV参考文档中详细介绍了对于CSV文件的读写等操作。

#!/usr/bin/python

import csv

csv_input = "csv_input.txt"
csv_output = "csv_output.csv"

csv_input_file = open(csv_input, 'rb')
csv_output_file = open(csv_output, 'wb')-------------------创建文件句柄

csv_reader = csv.reader(csv_input_file, delimiter=' ')-----创建读对象
csv_writer = csv.writer(csv_output_file)-------------------创建写对象
csv_writer.writerow(['Name', 'Age'])-----------------------写一行
for i in csv_reader:---------------------------------------遍历读句柄所有航
    print i[0], i[1]
    csv_writer.writerow([i[0], i[1]])

csv_input_file.close()
csv_output_file.close()------------------------------------关闭文件句柄

 测试程序:https://github.com/arnoldlu/pm-debug/tree/master/test/csv

 

5.3 Python时间戳以及定时器

在Linux下Python时间可以轻松达到微妙级别,但是在Window上精度只能达到毫秒级别。

time.sleep(duration)

睡眠一定时间,duration为秒数,类型可以为浮点数,所以可以小于1秒。

#!/usr/bin/python

import time
import datetime
import sched
import threading

sleep_duration = 0.001
sleep_count = 1000
start = datetime.datetime.utcnow()
for i in range(sleep_count):
    time.sleep(sleep_duration)
#    print i, datetime.datetime.utcnow()
end = datetime.datetime.utcnow()
print 'Time sleep total=', (end - start).total_seconds(), 'Sleep=', sleep_duration, 'Count=', sleep_count

 

结果如下:


Time sleep total= 1.090258 Sleep= 0.001 Count= 1000------------睡眠1000次,每次1ms,总共消耗1.09s

 

sched.scheduler.enter()

使用sched来执行timer功能,在超时函数中再次触发timer,以达到周期性功能。

#!/usr/bin/python
import time
import datetime
import sched

schedule = sched.scheduler(time.time, time.sleep)
schedule_expires = 0.001
schedule_count = 0 
schedule_max = 1000

def schedule_func(timer_start):
    global schedule_count

    #now = datetime.datetime.utcnow()
    #print 'count-', schedule_count, 'duration-', (now - start).total_seconds(), 'expire-', (now - timer_start).total_seconds()
    schedule_count += 1
    if schedule_count < schedule_max:
        schedule.enter(schedule_expires, 0, schedule_func, (datetime.datetime.utcnow(),))

start = datetime.datetime.utcnow()
schedule.enter(schedule_expires, 0, schedule_func, (datetime.datetime.utcnow(),))
schedule.run()
end = datetime.datetime.utcnow()
print 'Scheduler total=', (end - start).total_seconds(), 'Expire=', schedule_expires, 'Count=', schedule_max

 

结果如下:

Scheduler total= 1.107005 Expire= 0.001 Count= 1000-----------------------------消耗1.11s

第一个参数是一个整数或者float,代表多少秒后执行这个action任务。

第二个参数priority是优先级,0代表优先级最高,1次之,2次次之…当两个任务是预定在同一个时刻执行时,根据优先级决定谁先执行。

第三个参数就是你要执行的任务,可以简单的理解成你要执行的函数的函数名。

第四个参数是你要传入的这个定时执行的action为函数名的函数的参数,最好是用"()"括号来包起来,包起来肯定是不会出错的。其次,当你只传入一个参数时,用括号包起来后,一定要记住再打上一个逗号。即:schedule.enter(delay, priority, action, (argument1,))             虽然看起来有有点怪,但一定要这样,否则,会出现错误,比如你不打逗号,你传入一个字符串,它会以为你传入的是一个个字符,且每个字符的地位等于一个参数。总之切记,打上逗号,就安全了。否则会出问题的。 另外如果没有参数要传入,就直接传入空括号即可,即:schedule.enter(delay, priority, action, () )

threading.timer()

#!/usr/bin/python
import time
import datetime
import threading

threading_expires = 0.001
threading_count = 0
threading_max = 1000

def threading_func(timer_start):
    global threading_count

    #now = datetime.datetime.utcnow()
    #print 'count-', threading_count, 'duration-', (now - start).total_seconds(), 'expire-', (now - timer_start).total_seconds()
    threading_count += 1
    if threading_count < threading_max:
        threading.Timer(threading_expires, threading_func, (datetime.datetime.utcnow(),)).start()
    else:
    end = datetime.datetime.utcnow()

        print 'Threading total=', (end -start).total_seconds(), 'Expires=', threading_expires, 'Count=', threading_max
 

start = datetime.datetime.utcnow()
threading.Timer(threading_expires, threading_func, (datetime.datetime.utcnow(),)).start()

 

结果如下:

Threading total= 1.257532 Expires= 0.001 Count= 1000---------------------------共消耗1.26s

 

Windows下获取超过毫秒精度

#!/usr/bin/python
import time,ctypes

freq = ctypes.c_longlong(0)

ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(freq))
f=freq.value
print f

ctypes.windll.kernel32.QueryPerformanceCounter(ctypes.byref(freq))
n1=freq.value
print n1

time.sleep(1)

ctypes.windll.kernel32.QueryPerformanceCounter(ctypes.byref(freq))
n2=freq.value
print n2

print (n2-n1)/(f+0.)

 

结果如下:

3117382
2422873204863
2422876333399
1.00357800231

参考资料:深入学习python (六) 用sched来定时执行任务

测试程序:https://github.com/arnoldlu/pm-debug/tree/master/test/time_and_timer

5.4 PyVISA

针对测量仪器进行编程比较痛苦,存在各种各样的协议以及通过不同接口和总线(GPIB、USB、RS232)。使用任何一种语言去编程,你必须找到支持仪器和对应总线的合适的库。

为了解决这种问题,VISA应运而生。VISA即Virtual Instrument Software Architecture。VISA已经在主流的操作系统实现,并且不少供应商提供相应的库,部分可以免费下载。

VISA可以被任何能调用DLL库的语言使用,Python理所当然也支持。

安装对应设备提供的驱动程序,可以在设备控制台中看到对应的端口。Agilent示例如下,此时设备和接口都已经接通。

PyVISA安装

使用pip:    pip install pyvisa

使用easy_install:    easy_install pyvisa

或者下载源码到本地安装:    python setup.py install

PyVISA使用

读取66319D数据并且保存到csv文件中。

import visa
import time
import datetime
import numpy
import pandas as pd


measure_count = 0
measure_count_max = 100
results = pd.DataFrame()
temp = {}

rm = visa.ResourceManager()
res = rm.list_resources()
print "Find following resources: ", res
print("Opening " + res[-1])

inst = rm.open_resource(res[-1])


# Check if device avaiable
measure_start = time.time()
inst.query("*IDN?")
measure_end = time.time()
print 'Duration of IDN query:', str((measure_end-measure_start)*1000), 'ms'

#Set the power supply output
inst.write("INST P6V") # Select +6V output
inst.write("VOLT 4.0") # Set output voltage to 4.0 V
inst.write("CURR 2.0") # Set output current to 2.0 A

# Power off then power on, delay 1 second for stable
inst.write("OUTP OFF")
inst.write("OUTP ON")
time.sleep(1)

measure_start = time.time()
inst.query('MEAS:CURR?')
measure_end = time.time()
print 'Duration of MEAS:CURR? query:', '%.3f' % ((measure_end-measure_start)*1000), 'ms'

measure_start = datetime.datetime.utcnow()
while True:
    temp['start'] = (datetime.datetime.utcnow() - measure_start).total_seconds()
    temp['Imax'] = float(inst.query('MEAS:CURR:MAX?'))*1000
    temp['Icur'] = float(inst.query('MEAS:CURR:DC?'))*1000
    temp['Imin'] = float(inst.query('MEAS:CURR:MIN?'))*1000
    results = results.append(temp, ignore_index=True)
    measure_count += 1
    if measure_count > measure_count_max:
        break
measure_end = datetime.datetime.utcnow()
results.to_csv('results.csv')
print 'Measurement ending:', (measure_end-measure_start).total_seconds()

inst.write("OUTP OFF")

 

Windows平台结果如下:

Find following resources:  (u'GPIB0::5::INSTR',)
Opening GPIB0::5::INSTR
Duration of IDN query: 6.99996948242 ms-----------------时间太长,只是一个query
Duration of MEAS:CURR? query: 45.000 ms-----------------测量一次电流的脚本达到了45ms,太长...
Measurement ending: 13.913

 

参考文档

VISA Specifications:http://www.ivifoundation.org/specifications/default.aspx

PyVISA:https://pypi.python.org/pypi/PyVISA

PyVISA Documentation:https://pyvisa.readthedocs.io/en/stable/

测试程序:https://github.com/arnoldlu/pm-debug/tree/master/test/66319d

5.5 PySerial

由于ADB通信需要通过USB连接,而插入USB会导致系统进入充电模式,仪表电流倒灌。

进而导致电流读数为负值,这种情况无法准确判断功耗情况。

使用串口作为控制台不会导致这种问题,但是串口传输文件是个问题。

import time
import serial
import serial.tools.list_ports


def serial_fd_get():
    plist = list(serial.tools.list_ports.comports())---------------------------------------列举当前所有的串口信息

    if len(plist) <= 0:
        print "The Serial port can't find!"
    else:
        for i in range(len(plist)):--------------------------------------------------------轮询串口
            serialName = list(plist[i])[0]
            serialDescription = list(plist[i])[1]
            serialHwid = list(plist[i])[2]-------------------------------------------------列表中共三个信息:串口名,描述信息,硬件ID。其中串口名用来打开串口。
            print serialDescription
            if 'Silicon Labs CP210x USB to UART Bridge' in serialDescription:--------------根据描述信息获取需要的串口
                serialFd = serial.Serial(serialName,115200,timeout = 20)-------------------打开串口,获取文件句柄。
                print "check which port was really used >",serialFd.name, serialDescription, serialHwid
                isOpen = serialFd.isOpen()-------------------------------------------------判断能否正确打开
                if isOpen:
                    return serialFd
    return False
            
serialFd = serial_fd_get()------------------------------------------------------------------获取需要的串口句柄,否则轮询
while serialFd == False:
    print 'Please connect serial cable.'
    serialFd = serial_fd_get()
#Turn off tracing
result = serialFd.write('echo 0 >/sys/kernel/debug/tracing/tracing_on\n')------------------写串口
#Disable all traceevents
result = serialFd.write('echo 0 >/sys/kernel/debug/tracing/events/enable\n')
#Clear trace buffer
result = serialFd.write('echo > /sys/kernel/debug/tracing/trace\n')

#Turn on tracing
result = serialFd.write('echo 1 >/sys/kernel/debug/tracing/tracing_on\n')

#Duration of capture trace
time.sleep(500)

result = serialFd.write('cat /sys/kernel/debug/tracing/buffer_size_kb\n')
serialFd.readline()-----------------------------------------------------------------------读串口一行,还有read()/readlinses()等形式。
serialFd.readline()

#Disable all traceevents
result = serialFd.write('echo 0 >/sys/kernel/debug/tracing/events/enable\n')

#Save trace evnts
result = serialFd.write('cat /sys/kernel/debug/tracing/trace > /trace.txt\n')
time.sleep(5) #This is a MUST BE, or there is no time to finish the executing.

serialFd.close()--------------------------------------------------------------------------关文件句柄
serialFd.isOpen()

测试程序:https://github.com/arnoldlu/pm-debug/tree/master/test/PySerial

5.6 Python多线程以及线程同步

测试程序:https://github.com/arnoldlu/pm-debug/tree/master/test/multithread

5.7 PrettyTable

在Python中想简单打印文本,但是有需要有Table格式,很多情况下对齐是个问题。

好在prettytable提供了简单的功能。

from prettytable import PrettyTable

col = PrettyTable()

col.add_column("City name",["Adelaide","Brisbane","Darwin","Hobart","Sydney","Melbourne","Perth"])
col.add_column("Area", [1295, 5905, 112, 1357, 2058, 1566, 5386])
col.add_column("Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769])
col.add_column("Annual Rainfall",[600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4])
print col

mix = PrettyTable()
mix.field_names = ['City name', 'Area']
mix.add_row(["Adelaide",1295])
mix.add_row(["Brisbane",5905])
mix.add_row(["Darwin", 112])
mix.add_row(["Hobart", 1357])
mix.add_row(["Sydney", 2058])
mix.add_row(["Melbourne", 1566])
mix.add_row(["Perth", 5386])
mix.add_column("Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769])
mix.add_column("Annual Rainfall",[600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4])

print mix

 

结果如下:

+-----------+------+------------+-----------------+
| City name | Area | Population | Annual Rainfall |
+-----------+------+------------+-----------------+
|  Adelaide | 1295 |  1158259   |      600.5      |
|  Brisbane | 5905 |  1857594   |      1146.4     |
|   Darwin  | 112  |   120900   |      1714.7     |
|   Hobart  | 1357 |   205556   |      619.5      |
|   Sydney  | 2058 |  4336374   |      1214.8     |
| Melbourne | 1566 |  3806092   |      646.9      |
|   Perth   | 5386 |  1554769   |      869.4      |
+-----------+------+------------+-----------------+
+-----------+------+------------+-----------------+
| City name | Area | Population | Annual Rainfall |
+-----------+------+------------+-----------------+
|  Adelaide | 1295 |  1158259   |      600.5      |
|  Brisbane | 5905 |  1857594   |      1146.4     |
|   Darwin  | 112  |   120900   |      1714.7     |
|   Hobart  | 1357 |   205556   |      619.5      |
|   Sydney  | 2058 |  4336374   |      1214.8     |
| Melbourne | 1566 |  3806092   |      646.9      |
|   Perth   | 5386 |  1554769   |      869.4      |
+-----------+------+------------+-----------------+

测试程序: https://github.com/arnoldlu/pm-debug/tree/master/test/prettytable

 

6. 实现机制

6.1 Power、Trace、主线程间同步

Power线程一个锁power_lock,Trace线程一个锁trace_lock。

在主线程启动之后,获取power_lock和trace_lock,这两个锁在Power和Trace线程都执行完毕后才会释放。

所以主线程一直等待,Power和Trace数据获取完毕后才会继续执行下面分析工作。

 

Power线程启动后需要等待Trace线程开始执行,所以使用power_lock锁住。

Trace线程设置完毕后,抓取Trace之前释放power_lock。此时Power线程和Trace线程同步开始工作。

 

在得到键盘停止命令后,停止功耗抓取。释放trace_lock,Trace停止抓取。

Trace线程释放主线程锁,执行数据处理。

主要实现:https://github.com/arnoldlu/pm-debug/blob/master/power_analysis.ipynb

6.2 PC时间域和Trace时间域统一

由于Power时间戳和Trace时间戳在两个时间域,他们要同步到一个时间轴上可能存在一点误差。

通过在Trace开始抓取的时候记录trace_measure_start的PC时间,以此为桥梁达到和Power时间的转换。

 

但是存在一个问题,Trace开始抓取不一定就有数据,可能会隔一段时间。

这时候就需要主动往/sys/kernel/debug/tracing/trace_marker中写入MEASURE_START作为起始标志,在结束的时候写入MEASURE_END作为结束标志。

 

6.3 Trace数据解析

使用正则表达式来解析Trace每一行文本。主要分为两个步骤:

1.通用部分,解析出进程相关的进程名和进程ID,CPU,flag,时间戳,和消息体。

2.根据每种类型Trace的特性,分情况解析suspend/resume、irq、wakesource、cpuidle、cpufreq。

这两步之后Trace的相关数据就获取到了。

主要实现:https://github.com/arnoldlu/pm-debug/blob/master/analyze_ftrace.py

6.4 数据处理

抓取到功耗数据和Trace数据进行预处理,并将两者的时间域同步之后,数据准备工作就结束了。

下面就是进行数据的分析,分两部分一是输出功耗和内核行为图表,二是输出总结文档。

主要实现:https://github.com/arnoldlu/pm-debug/blob/master/power_analysis.ipynb

6.4.1 输出图表分析

输出的svg图分为5行:

1-是功耗数据曲线图,将系统Running状态覆盖在功耗图上。同时对超出阈值电流一段时间的区域进行高亮,并给出耗电量和平均电流。

2-是中断例程耗时图,同一中断在一行,超出一定时间的中断显示中断名称。

3-是唤醒源耗时图,按唤醒源分行,超出一定时间显示唤醒源名称。

4-是cpuidle耗时图,这两面有两种状态深睡对应紫色区域,浅睡对应黄色区域。

5-是cpu频率变化曲线。

6.4.2 输出总结分析

这里面和上面图表分析内容有部分重复,但是时间不同。

比较重要的两部分是IRQ和Wakesource。这两部分的统计信息对于了解中断和唤醒源的耗时平均值、最大值、最小值、频繁程度、均方差都很有效。

 

 

 

7. 功耗分析

有了上面的图表和总结输出,可以按照下面流程对异常功耗进行分析。

1. 先看功耗图形,红色区域是异常点。然后看此区域都被哪个核的Running状态覆盖,就可以知道是哪个核引起的功耗增加。

2. 确定是哪个核之后,然后在分析细节中断/wakesource等等。是哪个中断唤醒的,保持唤醒多久。这时可以打开更丰富的log显示细节。比如哪个进程、哪个CPU等等。

3. 然后再看看此时的cpuidle和cpufreq等是否正常。

 

下面看一个由Linux中断唤醒导致的功耗。

1-看出功耗和Running的关系,在Running前功耗已经起来,这是由于软件要滞后于中断唤醒;

2-在运行一段时间之后,功耗就跳变,可以对照cpuidle,此时CPU进入了idle状态。

3-同时CPU频率由624M降到312M。

4-在来到中断一行,可以看出唤醒中断和其他Running下运行的硬中断。

5-两个重要的唤醒源都保持了1s左右,这也是系统保持唤醒超过1s的主要原因。

 

 

8. 开发平台Jupyter Notebook

Anaconda Download下载开发环境,安装好后选择Jupyter Notebook。

8.1 添加扩展功能

pip install jupyter_contrib_nbextensions
安装之后可以在Nbextensions页面进行扩展功能的配置,打开Table of Contents、Exectute Time等功能。

8.2 使用方法

如果修改了库文件,需要Kernel->Restart & Run All。

可以添加目录,像草稿本一样。#是一层,##是二层,以此类推。

File->Download as将文件保存为其他形式。

9. 小结

通过工具化,尽量减少工作量,同时也能避免人操作带来的不确定行。

对功耗异常点的自动分析,将异常点相关行为记录显示,方便发现问题。

基于此,还可以进行扩展,可能优化点:

1. suspend/resume优化流程,找出耗时异常部分。不停进行回归测试,防止某些设备引入长时间suspend、resume函数。

2. 在OPP数目较多时,评估不同Governor优劣。

3. cpuidle问题不太大,主要看是否尽量进入了深睡。

3. irq重点关注长时中断例程、不稳定中断例程。

4. 超时wakeup source重点关注超市误差是否太大,非超时wakeup source是否正确释放。

posted on 2017-09-12 16:11  ArnoldLu  阅读(186)  评论(0编辑  收藏  举报

导航