summaryrefslogtreecommitdiff
path: root/bin/explore-kconfig.py
blob: 95d251b7caba78b9bd44568efec21042c67a334b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3

"""explore-kconfig - Obtain build attributes of configuration variants

explore-kconfig obtains build attributes such as ROM or RAM usage of
configuration variants for a given software project. It works on random
configurations (--random) or in the neighbourhood of existing configurations
(--neighbourhood).

Supported projects must be configurable via kconfig and provide a command which
outputs a JSON dict of build attributes on stdout. Use
--{clean,build,attribute}-command to configure explore-kconfig for a project.

explore-kconfig places the experiment results (containing configurations, build
logs, and correspondnig attributes) in the current working directory. Use
analyze-kconfig to build a model once data acquisition is complete.
"""

import argparse
import logging
import os
import sys

from dfatool import kconfig


def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__
    )
    parser.add_argument(
        "--neighbourhood",
        type=str,
        help="Explore neighbourhood of provided .config file(s)",
    )
    parser.add_argument(
        "--log-level",
        default=logging.INFO,
        type=lambda level: getattr(logging, level.upper()),
        help="Set log level",
    )
    parser.add_argument(
        "--enumerate", action="store_true", help="Enumerate all valid configurations"
    )
    parser.add_argument(
        "--random",
        type=int,
        help="Explore a number of random configurations (make randconfig)",
    )
    parser.add_argument(
        "--with-neighbourhood",
        action="store_true",
        help="Explore neighbourhood of successful random configurations",
    )
    parser.add_argument(
        "--clean-command", type=str, help="Clean command", default="make clean"
    )
    parser.add_argument(
        "--build-command", type=str, help="Build command", default="make"
    )
    parser.add_argument(
        "--attribute-command",
        type=str,
        help="Attribute extraction command",
        default="make nfpvalues",
    )
    parser.add_argument(
        "--randconfig-command",
        type=str,
        help="Randconfig command for --random",
        default="make randconfig",
    )
    parser.add_argument(
        "--kconfig-file", type=str, help="Kconfig file", default="Kconfig"
    )
    parser.add_argument("project_root", type=str, help="Project root directory")

    args = parser.parse_args()

    if isinstance(args.log_level, int):
        logging.basicConfig(level=args.log_level)
    else:
        print(f"Invalid log level. Setting log level to INFO.", file=sys.stderr)

    kconf = kconfig.KConfig(args.project_root)

    if args.clean_command:
        kconf.clean_command = args.clean_command
    if args.build_command:
        kconf.build_command = args.build_command
    if args.attribute_command:
        kconf.attribute_command = args.attribute_command
    if args.randconfig_command:
        kconf.randconfig_command = args.randconfig_command
    if args.kconfig_file:
        kconf.kconfig = args.kconfig_file

    kconf.run_nfpkeys()

    if args.enumerate:
        kconf.enumerate()

    if args.random:
        num_successful = 0
        # Assumption: At least 1% of builds are successful
        for i in range(args.random * 100):
            logging.info(f"Running randconfig {num_successful+1} of {args.random}")
            status = kconf.run_randconfig()
            if status["success"]:
                num_successful += 1
            if args.with_neighbourhood and status["success"]:
                config_filename = status["config_path"]
                logging.info(f"Exploring neighbourhood of {config_filename}")
                kconf.run_exploration_from_file(
                    config_filename, with_initial_config=False
                )
            if num_successful + 1 == args.random:
                break

    if args.neighbourhood:
        if os.path.isfile(args.neighbourhood):
            kconf.run_exploration_from_file(args.neighbourhood)
        elif os.path.isdir(args.neighbourhood):
            for filename in os.listdir(args.neighbourhood):
                config_filename = f"{args.neighbourhood}/{filename}"
                logging.info(f"Exploring neighbourhood of {config_filename}")
                kconf.run_exploration_from_file(config_filename)
        else:
            print(
                f"--neighbourhod: Error: {args.neighbourhood} must be a file or directory, but is neither",
                file=sys.stderr,
            )


if __name__ == "__main__":
    main()