aboutsummaryrefslogtreecommitdiffstats
path: root/damo_record.py
blob: 21d4259c3486205df756614a0c2859204d5812a3 (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# SPDX-License-Identifier: GPL-2.0

"""
Record monitored data access patterns.
"""

import json
import os
import signal
import subprocess
import time

import _damon
import _damon_args
import _damon_records

class DataForCleanup:
    kdamonds_idxs = None
    orig_kdamonds = None
    record_handle = None

data_for_cleanup = DataForCleanup()

cleaning = False
def cleanup_exit(exit_code):
    global cleaning
    if cleaning == True:
        return
    cleaning = True
    if data_for_cleanup.kdamonds_idxs != None:
        # ignore returning error, as kdamonds may already finished
        _damon.turn_damon_off(data_for_cleanup.kdamonds_idxs)
        err = _damon.stage_kdamonds(data_for_cleanup.orig_kdamonds)
        if err:
            print('failed restoring previous kdamonds setup (%s)' % err)

    if data_for_cleanup.record_handle:
        _damon_records.finish_recording(data_for_cleanup.record_handle)

    exit(exit_code)

def sighandler(signum, frame):
    print('\nsignal %s received' % signum)
    cleanup_exit(signum)

def handle_args(args):
    if _damon.any_kdamond_running() and not args.deducible_target:
        args.deducible_target = 'ongoing'

    args.output_permission, err = _damon_records.parse_file_permission_str(
            args.output_permission)
    if err != None:
        print('wrong --output permission (%s) (%s)' %
                (args.output_permission, err))
        exit(1)

    # backup duplicate output file
    if os.path.isfile(args.out):
        os.rename(args.out, args.out + '.old')

    err = _damon_records.set_perf_path(args.perf_path)
    if err != None:
        print(err)
        exit(-3)

class MemFootprintsSnapshot:
    time = None
    pid_statms = {}

    def __init__(self, pids):
        self.time = time.time()
        for pid in pids:
            with open('/proc/%s/statm' % pid, 'r') as f:
                self.pid_statms[pid] = f.read()

    def to_kvpairs(self):
        return {
                'time': self.time,
                'pid_statms': self.pid_statms
                }

def pid_running(pid):
    '''pid should be string'''
    try:
        subprocess.check_output(['ps', '--pid', pid])
        return True
    except:
        return False

def all_targets_terminated(targets):
    for target in targets:
        if pid_running('%s' % target.pid):
            return False
    return True

def poll_target_pids(kdamonds, add_childs):
    '''Return if polling should continued'''
    current_targets = kdamonds[0].contexts[0].targets
    if all_targets_terminated(current_targets):
        return False
    if not add_childs:
        return True

    for target in current_targets:
        if target.pid == None:
            continue
        try:
            childs_pids = subprocess.check_output(
                    ['ps', '--ppid', '%s' % target.pid, '-o', 'pid=']
                    ).decode().split()
        except:
            childs_pids = []
        if len(childs_pids) == 0:
            continue

        # TODO: Commit all at once, out of this loop
        new_targets = []
        for child_pid in childs_pids:
            # skip the child if already in the targets
            if child_pid in ['%s' % t.pid for t in current_targets]:
                continue
            # remove already terminated targets, since committing already
            # terminated targets to DAMON fails
            new_targets = [target for target in current_targets
                    if pid_running('%s' % target.pid)]
            new_targets.append(_damon.DamonTarget(pid=child_pid, regions=[]))
        if new_targets == []:
            continue

        # commit the new set of targets
        kdamonds[0].contexts[0].targets = new_targets
        err = _damon.commit(kdamonds)
        if err != None:
            # this might be not a problem; some of processes might
            # finished meanwhile
            print('adding child as target failed (%s)' % err)
            cleanup_exit(1)
    return True

def record_mem_footprint(kdamonds, snapshots):
    pids = []
    for kdamond in kdamonds:
        for ctx in kdamond.contexts:
            for target in ctx.targets:
                if target.pid is None:
                    continue
                pids.append(target.pid)
    snapshots.append(MemFootprintsSnapshot(pids))

def save_mem_footprint(snapshots, filepath, file_permission):
    with open(filepath, 'w') as f:
        json.dump([s.to_kvpairs() for s in snapshots], f, indent=4)
    os.chmod(filepath, file_permission)

def main(args):
    global data_for_cleanup

    is_ongoing = _damon_args.is_ongoing_target(args)
    _damon.ensure_root_and_initialized(args,
            load_feature_supports=is_ongoing,
            save_feature_supports=not is_ongoing)

    handle_args(args)

    # Setup for cleanup
    data_for_cleanup.orig_kdamonds = _damon.current_kdamonds()
    signal.signal(signal.SIGINT, sighandler)
    signal.signal(signal.SIGTERM, sighandler)

    # Now the real works
    if not is_ongoing:
        err, kdamonds = _damon_args.turn_damon_on(args)
        if err:
            print('could not turn DAMON on (%s)' % err)
            cleanup_exit(-2)
        data_for_cleanup.kdamonds_idxs = ['%d' % idx
                for idx, k in enumerate(kdamonds)]
        # TODO: Support multiple kdamonds, multiple contexts
        monitoring_intervals = kdamonds[0].contexts[0].intervals
    else:
        if not _damon.any_kdamond_running():
            print('DAMON is not turned on')
            exit(1)

        # TODO: Support multiple kdamonds, multiple contexts
        monitoring_intervals = data_for_cleanup.orig_kdamonds[
                0].contexts[0].intervals

    if args.schemes_target_regions == False:
        tracepoint = _damon_records.perf_event_damon_aggregated
    else:
        tracepoint = _damon_records.perf_event_damos_before_apply

    data_for_cleanup.record_handle = _damon_records.start_recording(
            tracepoint, args.out, args.output_type, args.output_permission,
            monitoring_intervals,
            profile=args.profile is True, profile_target_pid=None)
    print('Press Ctrl+C to stop')

    footprint_snapshots = []
    if _damon_args.self_started_target(args):
        while poll_target_pids(kdamonds, args.include_child_tasks):
            if args.footprint:
                record_mem_footprint(kdamonds, footprint_snapshots)
            time.sleep(1)

    if args.footprint:
        save_mem_footprint(footprint_snapshots, '%s.mem_footprint' % args.out,
                           args.output_permission)

    _damon.wait_kdamonds_turned_off()

    cleanup_exit(0)

def set_argparser(parser):
    parser = _damon_args.set_argparser(parser, add_record_options=True)
    parser.add_argument('--output_type',
                        choices=_damon_records.file_types,
                        default=_damon_records.file_type_json_compressed,
                        help='output file\'s type')
    parser.add_argument('--output_permission', type=str, default='600',
                        help='permission of the output file')
    parser.add_argument('--perf_path', type=str, default='perf',
                        help='path of perf tool ')
    parser.add_argument('--include_child_tasks', action='store_true',
                        help='record accesses of child processes')
    parser.add_argument('--schemes_target_regions', action='store_true',
                        help='record schemes tried to be applied regions')
    parser.add_argument('--profile', action='store_true',
                        help='record profiling information together')
    parser.add_argument('--footprint', action='store_true',
                        help='record memory footprint (VSZ and RSS)')
    parser.description = 'Record monitoring results'
    return parser