GNU make:计时构建,是否可以有一个目标,在所有目标完成后执行其配方?

5
我的问题类似于这个问题这个问题
本质上,我正在寻找类似以下内容的东西,或者是一个相当干净的解决方法:
GET_TIMESTAMP  = $(shell perl -e 'print time()')
START_TIME    := ${GET_TIMESTAMP}

all: T1 T2 ... TN

T1:
T2:
...:
TN:

#...

timer:
  @perl -e 'printf( "Loaded makefiles in %ds, build completed in %ds\n", $ARGV[1] - $ARGV[0], $ARGV[2] - $ARGV[1] );' ${START_TIME} ${LOAD_COMPLETE} ${GET_TIMESTAMP}

.ATEND: timer

LOAD_COMPLETE := ${GET_TIMESTAMP}

...这可能会以多种方式启动:

~ gmake all
(...)
  Loaded makefiles in 8s, build completed in 93s
~ gmake T2 T3 T4
(...)
  Loaded makefiles in 8s, build completed in 13s

核心思想是通过一个名为.ATEND的特殊目标,在所有CMDGOALSDEFAULTGOALS完成时触发某些操作。

6个回答

3
如果您只想计时构建,并且在类Unix平台上运行,为什么不直接使用time呢?
例如:
pax$ time sleep 1
    real    0m1.004s
    user    0m0.000s
    sys     0m0.000s

(虽然,在您的情况下,它当然是time gmake all。)
这似乎比试图在make中编写代码更加优雅,并使用了正确的工具。
或者,您可以修改规则本身,使其变成以下内容:
all: T1 T2 ... TN
    @perl -e blah blah blah

这将确保在所有目标完成后执行Perl - 您需要调整makefile以使其不是自动解决方案(最好限制自己只针对某些高级目标),但我认为这并不是一个巨大的负担,因为一旦正确设置,makefiles tend to be 相对地不易更改。

+1 是因为它确实是一个可行的替代方案。我希望它可以内置,这样你就可以随时使用而不需要额外的工具,但如果没有其他选择,这肯定是一个不错的替代方案。自定义解决方案的一部分是能够计算解析所有 makefile 所需的时间,但我可能不得不使用命令行工具和自定义目标来计时。 - Brian Vandenberg

2

我已经有一段时间在思考同样的问题,并尝试了一些方法。我猜递归make可能会做到,但我迄今为止都避免使用它。很遗憾没有.ATEND!

注意:显然,您只会显示重新制作食谱需要多长时间,除非从头开始。

时间

因此,正如paxdiablo建议的那样,Unix时间命令是最简单的方法(也是我过去所做的)。

% time make

基于食谱

为每个食谱添加一个计时器将给您累计运行时间,但不会告诉您每个食谱需要多长时间(虽然这不是问题所在)。 然而,对于特定的食谱,您可以再次使用时间,并甚至将输出复制到日志文件中(使用bash中的pipefail)。例如:

# SHELL = /bin/bash -o pipefail
# @bash -o pipefail -c ' ( time /home/matt/bin/example_stdouterr.bash ) |& tee z.log'
# @bash -o pipefail -c ' ( time /home/matt/bin/example_stdouterr.bash ) 2>&1 | tee z.log '

总结

我认为将其添加到大多数配方中是最好的选择。 然后您不需要进行特殊调用。 请参见下面的示例,其中应显示差异:

time -p make 
time -p make -j 2

例子

提供累积时间,以及时间戳。

TIME_START := $(shell date +%s)
define TIME-END
@time_end=`date +%s` ; time_exec=`awk -v "TS=${TIME_START}" -v "TE=$$time_end" 'BEGIN{TD=TE-TS;printf "%02dd:%02dh:%02dm:%02ds\n",TD/(60*60*24),TD/(60*60)%24,TD/(60)%60,TD%60}'` ; echo "##DATE end   `date '+%Y-%m-%d %H:%M:%S %Z'` cumulative $${time_exec} $@"
endef

PHONY_GOALS := all
all: toprule1
    $(TIME-END)

PHONY_GOALS += toprule1
toprule1: subrule1 subrule2
    @echo toprule 1 start
    @sleep 1
    @echo toprule 1 done
    $(TIME-END)

PHONY_GOALS += subrule1
subrule1: botrule1
    @echo subrule 1 start
    @sleep 1
    @echo subrule 1 done
    $(TIME-END)

PHONY_GOALS += subrule2
subrule2: botrule1
    @echo subrule 2 start
    @time -p sleep 2
    @echo subrule 2 done
    $(TIME-END)

PHONY_GOALS += botrule1
botrule1:
    @echo botrule 1 start
    @sleep 1
    @echo "botrule 1 done"
    $(TIME-END)

PHONY_GOALS += help
help:
    @echo "$(info All the goals are: ${PHONY_GOALS})"

########### end bit
.PHONY :${PHONY_GOALS}
OTHER_GOALS := ${filter-out ${PHONY_GOALS}, ${MAKECMDGOALS}}
${OTHER_GOALS}:

1

好的,我想到了一个想法并进行了编码。您可以设置一个计时器目标,该目标具有任何其他命令目标的前提条件,如果为空,则为所有目标。然后它只是:

make -j 2 subrule1 timer

以下是示例(如果它在同一食谱行上,它还会执行经过的计时器)。
# gnu make file

TIME_START := $(shell date +%s)
define TIME-END
@time_end=`date +%s` ; \
 time_exec=`awk -v "TS=${TIME_START}" -v "TE=$$time_end" 'BEGIN{TD=TE-TS;printf "%02dd:%02dh:%02dm:%02ds\n",TD/(60*60*24),TD/(60*60)%24,TD/(60)%60,TD%60}'` ; \
 echo "##DATE end   `date '+%Y-%m-%d %H:%M:%S %Z'` cumulative $${time_exec} $@"
endef

define ELAPSED-START
@elapsed_start=`date +%s`
endef
define ELAPSED-END
elapsed_end=`date +%s` ; \
 elapsed_exec=`awk -v "TS=$$elapsed_start" -v "TE=$$elapsed_end" 'BEGIN{TD=TE-TS;printf "%02dd:%02dh:%02dm:%02ds\n",TD/(60*60*24),TD/(60*60)%24,TD/(60)%60,TD%60}'` ; \
 echo "##DATE end   `date '+%Y-%m-%d %H:%M:%S %Z'` elapsed $${elapsed_exec} $@"
endef

PHONY_GOALS := all
all: subrule1 subrule2
    @$(TIME-END)

PHONY_GOALS += subrule1
subrule1: 
    $(ELAPSED-START) ; \
    echo subrule 1 start ; \
    sleep 1 ; \
    echo subrule 1 done ; \
    $(ELAPSED-END)
    @sleep 1
    @$(TIME-END)

PHONY_GOALS += subrule2
subrule2: 
    @echo subrule 2 start
    @$(ELAPSED-START) ; time -p sleep 2 ; $(ELAPSED-END)
    @echo subrule 2 done
    @$(TIME-END)

# create a prereq for timer to any CMDGOAL if empty add all
TIMERPREREQ := ${filter-out timer, ${MAKECMDGOALS}}
ifeq ($(strip $(TIMERPREREQ)),)
TIMERPREREQ := all
endif
#$(info TIMERPREREQ := ${TIMERPREREQ})

PHONY_GOALS += timer
timer: ${TIMERPREREQ}
    @$(TIME-END)

PHONY_GOALS += help
help:
    @echo "$(info All the goals are: ${PHONY_GOALS})"

########### end bit
.PHONY :${PHONY_GOALS}

结果如下:
subrule 1 start
subrule 1 done
##DATE end   2016-03-18 13:41:56 GMT elapsed 00d:00h:00m:01s subrule1
##DATE end   2016-03-18 13:41:57 GMT cumulative 00d:00h:00m:02s subrule1
##DATE end   2016-03-18 13:41:58 GMT cumulative 00d:00h:00m:02s timer

1

我曾在一个不能修改的构建系统中,尝试解决这个小问题(Makefiles只读)。

请将该文件另存为"timebash"并设置其执行权限:

#!/bin/bash
# shell replacement to time make recipes, invoke with:
#    make SHELL='/path/to/timebash "$(@)"' . . .
# timing data (start, elapsed) appears in the make output, along
# with a "make stack" tracked in the MKSTACK variable like
#    "target[0].subtarg[1].subtarg2[2]..."
target="$1"
shift
export MKSTACK
MKSTACK="${MKSTACK}${MKSTACK:+.}${target}[$MAKELEVEL]"

# compose your timing data format here
# on multiple lines here for readability
now=$(date "+%Y%m%d_%H%M%S")
fmt="timebash:$$"                ## each line starts with 'timebash'+pid
fmt="${fmt}:mkstack=${MKSTACK}"  ## add the MKSTACK
fmt="${fmt}:start=${now}"        ## wall clock at start
fmt="${fmt}:elapsed=%e"          ## time() fills this in
fmt="${fmt}:cmd='%C'"            ## time() fills this too

# now invoke time and the original make command
/usr/bin/time -f "$fmt" -- /bin/bash "${@}"

然后你可以这样调用make,而不需要更改任何Makefile。
make SHELL='/path/to/timebash "$(@)"' . . .

我编写了一个Python脚本来将时间数据渲染成ASCII甘特图,并成功地可视化了make时间。


0

我刚想到了一个解决方案:

GET_TIMESTAMP  = $(shell perl -e 'print time()')
START_TIME    := ${GET_TIMESTAMP}

all: T1 T2 ... TN

T1:
T2:
...:
TN:

timer_target : ${TIMER_DEPENDENCY}
  @echo do whatever to print out timing info

这可以从命令行中使用:

gmake timer_target TIMER_DEPENDENCY='T3 T4 T5'

...或类似的东西。

它不会对每个目标都无缝地工作,但它是可用的。


0

我希望你喜欢这个Makefile性能测量工具。

为了自己的使用,我编写了这个Python辅助脚本来外部维护Makefile经过的时间。它运行得非常好,并且有相当完善的文档。

使用这个脚本可以卸载“make”实用程序中的复杂和微妙的任务,并使Makefile编写者能够对制作性能报告进行精细控制。

#!/usr/bin/env python

"""
MakeTime.py(support)
NAME
    MakeTime.py - Maintain storage for Makefile rule elapsed times.
SYNOPSIS
    MakeTime.py [OPTION]
    MakeTime.py [label] [title text]
DESCRIPTION
    Hold timing data in the external file 'MakeTime.json'.
OPTIONS
    -c, --clean, --clear, --initialize
        Delete the external file.

    -s, --summary, --summarize, --report
        Generate summary of stored timing data.

    -h, --help, (anything else beginning with '-')
        Show this help

EXAMPLES
    ./MakeTime.py
    ./MakeTime.py -h
    ./MakeTime.py --help
        Show help

    ./MakeTime.py -c
    ./MakeTime.py --clean
    ./MakeTime.py --clear
    ./MakeTime.py --initialize
        Delete JSON file 'MakeTime.json' to prepare for new timings.

    ./MakeTime.py START How much time does the make execution take?
        Associate title and t0 for START rule

    ./MakeTime.py START
        Associate t1 to JSON for START rule

    ./MakeTime.py -r
    ./MakeTime.py -s
    ./MakeTime.py --report
    ./MakeTime.py --summary
    ./MakeTime.py --summarize
        Perform calculations for execution times then
        summarize collected timings with labels to help.


EXAMPLE MAKEFILE
###############################################################################
all:    START rule1 rule2 END

.PHONY:
START:
    @./MakeTime.py --clear
    @./MakeTime.py $@ Elapsed during the entire make process.

.PHONY:
rule1:
    @./MakeTime.py $@ Elapsed during rule1.
    sleep 3
    @./MakeTime.py $@

.PHONY:
rule2:
    @./MakeTime.py $@ Elapsed during rule2.
    sleep 5
    @./MakeTime.py $@

.PHONY:
END:
    @./MakeTime.py START
    @./MakeTime.py --summary
###############################################################################

COPYRIGHT
    Copyright(c)2016 Jonathan D. Lettvin, All Rights Reserved
"""

from time import time
from sys import argv
from pprint import pprint
from os import remove
from os.path import exists

store = "MakeTime.json"
result = {}
marked = time()


if len(argv) == 1:
    "When no args are given, display the help text."
    print __doc__

elif len(argv) == 2:
    arg1 = argv[1]

    if arg1 in ["-c", "--clean", "--clear", "--initialize"]:
        "Clear the backstore."
        if exists(store):
            remove(store)

    elif arg1 in ["-r", "-s", "--summary", "--summarize", "--report"]:
        "Calculate elapsed times and print summary"
        with open(store, "r") as source:
            names = []
            seen = set()
            for n, line in enumerate(source):
                data = eval(line)
                for k,v in data.iteritems():
                    result[k] = result.get(k, {})
                    result[k].update(v)
                    if not k in seen:
                        seen.add(k)
                        names.append(k)
        for k in names:
            v = result[k]
            timing = "unknown MakeTime"
            if v.has_key('t1') and v.has_key('t0'):
                timing = "%4.3f seconds" % (float(v['t1']) - float(v['t0']))
            print("\t\t%s [%s] %s" % (timing, k, v['title']))

    elif arg1[0] == '-':  # -, -h, --help, or anything beginning with '-'
        print __doc__

    else:
        "Per rule delta"
        result = {arg1: {'t1': marked}}
        with open(store, "a") as target:
            print>>target, str(result)

else:
    "Per rule t0"
    result = {argv[1]: {'t0': marked, 'title': " ".join(argv[2:])}}
    with open(store, "a") as target:
        print>>target, str(result)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接