simulators.py 18 KB
Newer Older
Antoine Kaufmann's avatar
Antoine Kaufmann committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Copyright 2021 Max Planck Institute for Software Systems, and
# National University of Singapore
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

23
24
25
# Allow own class to be used as type for a method's argument
from __future__ import annotations

26
import math
27
28
import typing as tp

29
30
from simbricks.orchestration.experiment.experiment_environment import ExpEnv
from simbricks.orchestration.nodeconfig import NodeConfig
31

32

33
class Simulator(object):
34
35
    """Base class for all simulators."""

36
37
    def __init__(self):
        self.extra_deps = []
Jonas Kaufmann's avatar
Jonas Kaufmann committed
38
        self.name = ''
39

40
    def resreq_cores(self):
41
        """Number of cores required for this simulator."""
42
43
44
        return 1

    def resreq_mem(self):
45
        """Memory required for this simulator (in MB)."""
46
47
        return 64

Jonas Kaufmann's avatar
Jonas Kaufmann committed
48
49
50
51
    def full_name(self):
        """Full name of the simulator."""
        return ''

52
    # pylint: disable=unused-argument
53
54
    def prep_cmds(self, env: ExpEnv) -> tp.List[str]:
        """Commands to run to prepare simulator."""
55
56
        return []

57
    # pylint: disable=unused-argument
58
59
    def run_cmd(self, env: ExpEnv) -> tp.Optional[str]:
        """Command to run to execute simulator."""
60
        return None
61

Jonas Kaufmann's avatar
Jonas Kaufmann committed
62
    def dependencies(self) -> tp.List[Simulator]:
63
        """Other simulators this one depends on."""
64
65
66
        return []

    # Sockets to be cleaned up
67
    # pylint: disable=unused-argument
68
    def sockets_cleanup(self, env: ExpEnv):
69
70
71
        return []

    # sockets to wait for indicating the simulator is ready
72
    # pylint: disable=unused-argument
73
    def sockets_wait(self, env: ExpEnv):
74
75
76
77
78
79
80
81
        return []

    def start_delay(self):
        return 5

    def wait_terminate(self):
        return False

82

83
class PCIDevSim(Simulator):
84
85
86
87
88
89
90
91
92
    """Base class for PCIe device simulators."""

    def __init__(self):
        super().__init__()

        self.sync_mode = 0
        self.start_tick = 0
        self.sync_period = 500
        self.pci_latency = 500
93
94
95
96

    def full_name(self):
        return 'dev.' + self.name

97
98
99
    def is_nic(self):
        return False

100
101
102
103
104
105
106
107
    def sockets_cleanup(self, env):
        return [env.dev_pci_path(self), env.dev_shm_path(self)]

    def sockets_wait(self, env):
        return [env.dev_pci_path(self)]


class NICSim(PCIDevSim):
108
109
110
111
112
113
114
115
    """Base class for NIC simulators."""

    def __init__(self):
        super().__init__()

        self.network: tp.Optional[NetSim] = None
        self.mac: tp.Optional[str] = None
        self.eth_latency = 500
116
117
        """Ethernet latency in nanoseconds from this NIC to the network
        component."""
118

Jonas Kaufmann's avatar
Jonas Kaufmann committed
119
    def set_network(self, net: NetSim):
120
        """Connect this NIC to a network simulator."""
121
122
123
        self.network = net
        net.nics.append(self)

124
    def basic_args(self, env, extra=None):
125
126
        cmd = (
            f'{env.dev_pci_path(self)} {env.nic_eth_path(self)}'
127
128
            f' {env.dev_shm_path(self)} {self.sync_mode} {self.start_tick}'
            f' {self.sync_period} {self.pci_latency} {self.eth_latency}'
129
        )
130
131
        if self.mac is not None:
            cmd += ' ' + (''.join(reversed(self.mac.split(':'))))
132
133
134
135

        if extra is not None:
            cmd += ' ' + extra
        return cmd
136

137
    def basic_run_cmd(self, env, name, extra=None):
138
        cmd = f'{env.repodir}/sims/nic/{name} {self.basic_args(env, extra)}'
139
140
        return cmd

141
    def full_name(self):
142
        return 'nic.' + self.name
143

144
145
146
    def is_nic(self):
        return True

147
    def sockets_cleanup(self, env):
148
        return super().sockets_cleanup(env) + [env.nic_eth_path(self)]
149
150

    def sockets_wait(self, env):
151
        return super().sockets_wait(env) + [env.nic_eth_path(self)]
152

Jonas Kaufmann's avatar
Jonas Kaufmann committed
153

154
class NetSim(Simulator):
155
    """Base class for network simulators."""
156
157
158
159
160
161
162

    def __init__(self):
        super().__init__()

        self.opt = ''
        self.sync_mode = 0
        self.sync_period = 500
163
164
        """Synchronization period in nanoseconds from this network to connected
        components."""
165
        self.eth_latency = 500
166
167
        """Ethernet latency in nanoseconds from this network to connected
        components."""
168
169
170
171
        self.nics: list[NICSim] = []
        self.hosts_direct: list[HostSim] = []
        self.net_listen: list[NetSim] = []
        self.net_connect: list[NetSim] = []
172
173

    def full_name(self):
174
        return 'net.' + self.name
175

176
    def connect_network(self, net: NetSim):
177
        """Connect this network to the listening peer `net`"""
178
179
180
        net.net_listen.append(self)
        self.net_connect.append(net)

Jonas Kaufmann's avatar
Jonas Kaufmann committed
181
    def connect_sockets(self, env: ExpEnv) -> tp.List[tp.Tuple[Simulator, str]]:
182
183
184
        sockets = []
        for n in self.nics:
            sockets.append((n, env.nic_eth_path(n)))
185
186
        for n in self.net_connect:
            sockets.append((n, env.n2n_eth_path(n, self)))
187
188
        for h in self.hosts_direct:
            sockets.append((h, env.net2host_eth_path(self, h)))
189
190
        return sockets

191
    def listen_sockets(self, env: ExpEnv):
192
193
194
195
196
        listens = []
        for net in self.net_listen:
            listens.append((net, env.n2n_eth_path(self, net)))
        return listens

197
    def dependencies(self):
198
        return self.nics + self.net_connect + self.hosts_direct
199

200
    def sockets_cleanup(self, env: ExpEnv):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
201
        return [s for (_, s) in self.listen_sockets(env)]
202

203
    def sockets_wait(self, env: ExpEnv):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
204
        return [s for (_, s) in self.listen_sockets(env)]
205

206

207
class HostSim(Simulator):
208
    """Base class for host simulators."""
209

210
    def __init__(self, node_config: NodeConfig):
211
        super().__init__()
212
        self.node_config = node_config
213
        """System configuration for this simulated host. """
214
215
        self.wait = False
        """
216
217
218
        `True` - Wait for this simulator to finish execution. `False` - Don't
        wait and instead shutdown the simulator as soon as all other awaited
        simulators have completed execution.
219
220
221
222
223
224
225
226
        """
        self.sleep = 0
        self.cpu_freq = '8GHz'

        self.sync_mode = 0
        self.sync_period = 500
        self.pci_latency = 500

227
        self.pcidevs: tp.List[PCIDevSim] = []
228
        self.net_directs: tp.List[NetSim] = []
229

230
231
232
233
    @property
    def nics(self):
        return filter(lambda pcidev: pcidev.is_nic(), self.pcidevs)

234
235
236
237
    def full_name(self):
        return 'host.' + self.name

    def add_nic(self, dev: NICSim):
238
        """Add a NIC to this host."""
239
240
        self.add_pcidev(dev)

241
    def add_pcidev(self, dev: PCIDevSim):
242
        """Add a PCIe device to this host."""
243
244
245
        dev.name = self.name + '.' + dev.name
        self.pcidevs.append(dev)

246
    def add_netdirect(self, net: NetSim):
247
        """Add a direct connection to a network to this host."""
248
249
250
        net.hosts_direct.append(self)
        self.net_directs.append(net)

251
252
253
254
255
256
257
258
259
260
261
262
    def dependencies(self):
        deps = []
        for dev in self.pcidevs:
            deps.append(dev)
            if isinstance(dev, NICSim):
                deps.append(dev.network)
        return deps

    def wait_terminate(self):
        return self.wait


263
class QemuHost(HostSim):
264
265
    """Qemu host simulator."""

266
267
    def __init__(self, node_config: NodeConfig):
        super().__init__(node_config)
268
269
270

        self.sync = False
        self.cpu_freq = '4GHz'
271

272
    def resreq_cores(self):
273
274
275
276
        if self.sync:
            return 1
        else:
            return self.node_config.cores + 1
277
278

    def resreq_mem(self):
Hejing Li's avatar
Hejing Li committed
279
        return 8192
280
281

    def prep_cmds(self, env):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
282
283
        return [
            f'{env.qemu_img_path} create -f qcow2 -o '
284
            f'backing_file="{env.hd_path(self.node_config.disk_image)}" '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
285
286
            f'{env.hdcopy_path(self)}'
        ]
287
288

    def run_cmd(self, env):
289
        accel = ',accel=kvm:tcg' if not self.sync else ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
290
291
        cmd = (
            f'{env.qemu_path} -machine q35{accel} -serial mon:stdio '
292
            '-cpu Skylake-Server -display none -nic none '
293
294
295
            f'-kernel {env.qemu_kernel_path} '
            f'-drive file={env.hdcopy_path(self)},if=ide,index=0,media=disk '
            f'-drive file={env.cfgtar_path(self)},if=ide,index=1,media=disk,'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
296
            'driver=raw '
297
            '-append "earlyprintk=ttyS0 console=ttyS0 root=/dev/sda1 '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
298
299
300
            'init=/home/ubuntu/guestinit.sh rw" '
            f'-m {self.node_config.memory} -smp {self.node_config.cores} '
        )
301
302

        if self.sync:
303
304
305
306
307
308
309
310
311
312
            unit = self.cpu_freq[-3:]
            if unit.lower() == 'ghz':
                base = 0
            elif unit.lower() == 'mhz':
                base = 3
            else:
                raise Exception('cpu frequency specified in unsupported unit')
            num = float(self.cpu_freq[:-3])
            shift = base - int(math.ceil(math.log(num, 2)))

313
            cmd += f' -icount shift={shift},sleep=off '
314

315
        for dev in self.pcidevs:
316
            cmd += f'-device simbricks-pci,socket={env.dev_pci_path(dev)}'
317
318
319
320
321
322
323
324
            if self.sync:
                cmd += ',sync=on'
                cmd += f',pci-latency={self.pci_latency}'
                cmd += f',sync-period={self.sync_period}'
            else:
                cmd += ',sync=off'
            cmd += ' '

325
        # qemu does not currently support net direct ports
326
        assert len(self.net_directs) == 0
327
328
        return cmd

Jonas Kaufmann's avatar
Jonas Kaufmann committed
329

330
class Gem5Host(HostSim):
331
    """Gem5 host simulator."""
332

333
334
335
    def __init__(self, node_config: NodeConfig):
        node_config.sim = 'gem5'
        super().__init__(node_config)
336
337
338
        self.cpu_type_cp = 'X86KvmCPU'
        self.cpu_type = 'TimingSimpleCPU'
        self.sys_clock = '1GHz'
339
340
        self.extra_main_args = []
        self.extra_config_args = []
341
        self.variant = 'fast'
342

343
344
345
346
347
348
    def resreq_cores(self):
        return 1

    def resreq_mem(self):
        return 4096

349
350
    def prep_cmds(self, env):
        return [f'mkdir -p {env.gem5_cpdir(self)}']
351
352
353
354
355
356

    def run_cmd(self, env):
        cpu_type = self.cpu_type
        if env.create_cp:
            cpu_type = self.cpu_type_cp

357
        cmd = f'{env.gem5_path(self.variant)} --outdir={env.gem5_outdir(self)} '
358
        cmd += ' '.join(self.extra_main_args)
Jonas Kaufmann's avatar
Jonas Kaufmann committed
359
360
        cmd += (
            f' {env.gem5_py_path} --caches --l2cache --l3cache '
361
            '--l1d_size=32kB --l1i_size=32kB --l2_size=2MB --l3_size=32MB '
362
            '--l1d_assoc=8 --l1i_assoc=8 --l2_assoc=4 --l3_assoc=16 '
363
364
            f'--cacheline_size=64 --cpu-clock={self.cpu_freq}'
            f' --sys-clock={self.sys_clock} '
365
366
            f'--checkpoint-dir={env.gem5_cpdir(self)} '
            f'--kernel={env.gem5_kernel_path} '
367
            f'--disk-image={env.hd_raw_path(self.node_config.disk_image)} '
368
            f'--disk-image={env.cfgtar_path(self)} '
369
            f'--cpu-type={cpu_type} --mem-size={self.node_config.memory}MB '
370
            f'--num-cpus={self.node_config.cores} '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
371
372
            '--ddio-enabled --ddio-way-part=8 --mem-type=DDR4_2400_16x4 '
        )
373

374
375
        if env.create_cp:
            cmd += '--max-checkpoints=1 '
376

377
        if env.restore_cp:
378
            cmd += '-r 1 '
379

380
        for dev in self.pcidevs:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
381
382
383
384
385
            cmd += (
                f'--simbricks-pci=connect:{env.dev_pci_path(dev)}'
                f':latency={self.pci_latency}ns'
                f':sync_interval={self.sync_period}ns'
            )
386
            if cpu_type == 'TimingSimpleCPU':
387
                cmd += ':sync'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
388
            cmd += ' '
389
390

        for net in self.net_directs:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
391
392
393
394
395
396
397
            cmd += (
                '--simbricks-eth-e1000=listen'
                f':{env.net2host_eth_path(net, self)}'
                f':{env.net2host_shm_path(net, self)}'
                f':latency={net.eth_latency}ns'
                f':sync_interval={net.sync_period}ns'
            )
398
399
            if cpu_type == 'TimingSimpleCPU':
                cmd += ':sync'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
400
            cmd += ' '
401
402

        cmd += ' '.join(self.extra_config_args)
403
404
405
406
        return cmd


class CorundumVerilatorNIC(NICSim):
407
408
409
410

    def __init__(self):
        super().__init__()
        self.clock_freq = 250  # MHz
411

412
413
414
415
416
    def resreq_mem(self):
        # this is a guess
        return 512

    def run_cmd(self, env):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
417
418
419
420
        return self.basic_run_cmd(
            env, '/corundum/corundum_verilator', str(self.clock_freq)
        )

421
422

class CorundumBMNIC(NICSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
423

424
    def run_cmd(self, env):
Hejing Li's avatar
Hejing Li committed
425
        return self.basic_run_cmd(env, '/corundum_bm/corundum_bm')
426

Jonas Kaufmann's avatar
Jonas Kaufmann committed
427

428
class I40eNIC(NICSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
429

430
    def run_cmd(self, env):
Hejing Li's avatar
Hejing Li committed
431
        return self.basic_run_cmd(env, '/i40e_bm/i40e_bm')
432

Jonas Kaufmann's avatar
Jonas Kaufmann committed
433

434
class E1000NIC(NICSim):
435
436
437
438

    def __init__(self):
        super().__init__()
        self.debug = False
Jonas Kaufmann's avatar
Jonas Kaufmann committed
439

440
    def run_cmd(self, env):
441
442
443
444
        cmd = self.basic_run_cmd(env, '/e1000_gem5/e1000_gem5')
        if self.debug:
            cmd = 'env E1000_DEBUG=1 ' + cmd
        return cmd
445

Jonas Kaufmann's avatar
Jonas Kaufmann committed
446

447
448
449
450
class MultiSubNIC(NICSim):

    def __init__(self, mn):
        super().__init__()
451
        self.multinic = mn
452
453
454
455
456
457
458
459
460
461

    def full_name(self):
        return self.multinic.full_name() + '.' + self.name

    def dependencies(self):
        return super().dependencies() + [self.multinic]

    def start_delay(self):
        return 0

Jonas Kaufmann's avatar
Jonas Kaufmann committed
462

Jonas Kaufmann's avatar
Jonas Kaufmann committed
463
class I40eMultiNIC(Simulator):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
464

465
466
    def __init__(self):
        super().__init__()
467
        self.subnics = []
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484

    def create_subnic(self):
        sn = MultiSubNIC(self)
        self.subnics.append(sn)
        return sn

    def full_name(self):
        return 'multinic.' + self.name

    def run_cmd(self, env):
        args = ''
        first = True
        for sn in self.subnics:
            if not first:
                args += ' -- '
            first = False
            args += sn.basic_args(env)
485
        return f'{env.repodir}/sims/nic/i40e_bm/i40e_bm {args}'
486
487
488
489
490
491
492
493
494
495
496
497

    def sockets_cleanup(self, env):
        ss = []
        for sn in self.subnics:
            ss += sn.sockets_cleanup(env)
        return ss

    def sockets_wait(self, env):
        ss = []
        for sn in self.subnics:
            ss += sn.sockets_wait(env)
        return ss
498
499
500


class WireNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
501

502
    def run_cmd(self, env):
Jialin Li's avatar
Jialin Li committed
503
        connects = self.connect_sockets(env)
504
        assert len(connects) == 2
505
506
        cmd = (
            f'{env.repodir}/sims/net/wire/net_wire {connects[0][1]}'
507
            f' {connects[1][1]} {self.sync_mode} {self.sync_period}'
508
509
            f' {self.eth_latency}'
        )
510
511
512
        if len(env.pcap_file) > 0:
            cmd += ' ' + env.pcap_file
        return cmd
513

Jonas Kaufmann's avatar
Jonas Kaufmann committed
514

515
class SwitchNet(NetSim):
516
517
518
519

    def __init__(self):
        super().__init__()
        self.sync = True
520

521
    def run_cmd(self, env):
522
        cmd = env.repodir + '/sims/net/switch/net_switch'
523
        cmd += f' -S {self.sync_period} -E {self.eth_latency}'
524
525
526
527

        if not self.sync:
            cmd += ' -u'

528
529
        if len(env.pcap_file) > 0:
            cmd += ' -p ' + env.pcap_file
Jonas Kaufmann's avatar
Jonas Kaufmann committed
530
        for (_, n) in self.connect_sockets(env):
531
            cmd += ' -s ' + n
Jonas Kaufmann's avatar
Jonas Kaufmann committed
532
        for (_, n) in self.listen_sockets(env):
533
            cmd += ' -h ' + n
534
        return cmd
535

536
537
538
539
540
541
542
543
544
    def sockets_cleanup(self, env):
        # cleanup here will just have listening eth sockets, switch also creates
        # shm regions for each with a "-shm" suffix
        cleanup = []
        for s in super().sockets_cleanup(env):
            cleanup.append(s)
            cleanup.append(s + '-shm')
        return cleanup

Jonas Kaufmann's avatar
Jonas Kaufmann committed
545

546
class TofinoNet(NetSim):
547
548
549
550
551

    def __init__(self):
        super().__init__()
        self.tofino_log_path = '/tmp/model.ldjson'
        self.sync = True
Jialin Li's avatar
Jialin Li committed
552

553
    def run_cmd(self, env):
554
555
556
557
558
        cmd = f'{env.repodir}/sims/net/tofino/tofino'
        cmd += (
            f' -S {self.sync_period} -E {self.eth_latency}'
            f' -t {self.tofino_log_path}'
        )
559
560
        if not self.sync:
            cmd += ' -u'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
561
        for (_, n) in self.connect_sockets(env):
562
            cmd += ' -s ' + n
563
        return cmd
564

Jonas Kaufmann's avatar
Jonas Kaufmann committed
565

566
class NS3DumbbellNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
567

568
569
    def run_cmd(self, env):
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
570
        for (n, s) in self.connect_sockets(env):
571
            if 'server' in n.name:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
572
                ports += f'--CosimPortLeft={s} '
573
            else:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
574
                ports += f'--CosimPortRight={s} '
575

576
577
578
579
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
            f'/cosim-run.sh cosim cosim-dumbbell-example {ports} {self.opt}'
        )
580
581
582
583
        print(cmd)

        return cmd

Jonas Kaufmann's avatar
Jonas Kaufmann committed
584

585
class NS3BridgeNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
586

587
588
    def run_cmd(self, env):
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
589
        for (_, n) in self.connect_sockets(env):
590
            ports += '--CosimPort=' + n + ' '
591

592
593
594
595
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
            f'/cosim-run.sh cosim cosim-bridge-example {ports} {self.opt}'
        )
596
597
598
        print(cmd)

        return cmd
599

Jonas Kaufmann's avatar
Jonas Kaufmann committed
600

601
class NS3SequencerNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
602

603
604
    def run_cmd(self, env):
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
605
        for (n, s) in self.connect_sockets(env):
606
            if 'client' in n.name:
607
                ports += '--ClientPort=' + s + ' '
608
            elif 'replica' in n.name:
609
                ports += '--ServerPort=' + s + ' '
610
            elif 'sequencer' in n.name:
611
                ports += '--ServerPort=' + s + ' '
612
613
            else:
                raise Exception('Wrong NIC type')
614
615
616
617
618
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
            f'/cosim-run.sh sequencer sequencer-single-switch-example'
            f' {ports} {self.opt}'
        )
619
620
621
        return cmd


622
class FEMUDev(PCIDevSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
623

624
    def run_cmd(self, env):
625
626
627
628
        cmd = (
            f'{env.repodir}/sims/external/femu/femu-simbricks'
            f' {env.dev_pci_path(self)} {env.dev_shm_path(self)}'
        )
629
        return cmd