From fa13d0ecbd1de8c23f27567f7ca026be90356b2d Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 26 Nov 2024 13:22:04 +0800 Subject: [PATCH] ebpf: Add cachestat tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use cachestat to trace who generates large pagechae,and which file it is. The idea refers to upstream talk: https://github.com/iovisor/bcc/issues/237 sudo ./spycat cachestat --upload-rate 10s Signed-off-by: Tao Chen --- Makefile | 5 +- pkg/app/cmd.go | 29 +++ pkg/app/config/config.go | 12 + .../detector/memdetector/memdetector.go | 54 +++- pkg/component/processor/processor.go | 2 + pkg/core/model/const.go | 7 + pkg/ebpf/mem/cachestat.go | 246 ++++++++++++++++++ pkg/ebpf/mem/cachestat/cachestat.bpf.c | 122 +++++++++ pkg/ebpf/mem/cachestat/cachestat.h | 55 ++++ test/pagecache.py | 33 +++ 10 files changed, 561 insertions(+), 4 deletions(-) create mode 100644 pkg/ebpf/mem/cachestat.go create mode 100644 pkg/ebpf/mem/cachestat/cachestat.bpf.c create mode 100644 pkg/ebpf/mem/cachestat/cachestat.h create mode 100644 test/pagecache.py diff --git a/Makefile b/Makefile index 74f434e..b7deb46 100644 --- a/Makefile +++ b/Makefile @@ -41,8 +41,8 @@ CLANG_COMPILE := $(CLANG) $(CFLAGS) $(VMLINUX) $(BPF_HEADER) -target bpf -D__TAR generate: export BPF_CLANG := $(CLANG) generate: export BPF_CFLAGS := $(CFLAGS) -generate: - go generate $(EBPF_SRC)/uprobe/uprobe.go +#generate: +# go generate $(EBPF_SRC)/uprobe/uprobe.go libbpf: make -C $(LIBBPF_SRC) libbpf @@ -53,6 +53,7 @@ ebpf.o: libbpf $(CLANG_COMPILE) -c $(EBPF_SRC)/cpu/oncpu/oncpu.bpf.c -o $(EBPF_SRC)/cpu/oncpu/oncpu.bpf.o $(CLANG_COMPILE) -c $(EBPF_SRC)/cpu/futexsnoop/futexsnoop.bpf.c -o $(EBPF_SRC)/cpu/futexsnoop/futexsnoop.bpf.o $(CLANG_COMPILE) -c $(EBPF_SRC)/cpu/syscall/syscall.bpf.c -o $(EBPF_SRC)/cpu/syscall/syscall.bpf.o + $(CLANG_COMPILE) -c $(EBPF_SRC)/mem/cachestat/cachestat.bpf.c -o $(EBPF_SRC)/mem/cachestat/cachestat.bpf.o all: generate ebpf.o @echo "go build $(TARGET)" diff --git a/pkg/app/cmd.go b/pkg/app/cmd.go index 894573d..b317470 100644 --- a/pkg/app/cmd.go +++ b/pkg/app/cmd.go @@ -14,6 +14,7 @@ import ( "github.com/chentao-kernel/spycat/pkg/core" "github.com/chentao-kernel/spycat/pkg/core/model" "github.com/chentao-kernel/spycat/pkg/ebpf/cpu" + "github.com/chentao-kernel/spycat/pkg/ebpf/mem" "github.com/chentao-kernel/spycat/pkg/log" "github.com/fatih/color" "github.com/pyroscope-io/pyroscope/pkg/cli" @@ -63,6 +64,7 @@ func SubCmdInit(cmd *Cmd) { newOnCpuSpyCmd(&cmd.cfg.ONCPU), newFutexSnoopSpyCmd(&cmd.cfg.FUTEXSNOOP), newSyscallSpyCmd(&cmd.cfg.SYSCALL), + newCacheStatSpyCmd(&cmd.cfg.CACHESTAT), newVersionCmd(), } @@ -235,6 +237,33 @@ func newSyscallSpyCmd(cfg *config.SYSCALL) *cobra.Command { return connectCmd } +func newCacheStatSpyCmd(cfg *config.CACHESTAT) *cobra.Command { + vpr := newViper() + + connectCmd := &cobra.Command{ + Use: "cachestat [flags]", + Short: "eBPF snoop cachestat", + Args: cobra.NoArgs, + + RunE: cli.CreateCmdRunFn(cfg, vpr, func(_ *cobra.Command, _ []string) error { + conf := &appspy.Config{ + Exporter: cfg.Exporter, + Server: cfg.Server, + } + return RunSpy(cfg, conf, func(cfg interface{}, buf chan *model.SpyEvent) core.BpfSpyer { + config, ok := cfg.(*config.CACHESTAT) + if ok { + return mem.NewCacheStatBpfSession(model.CacheStat, config, buf) + } + return nil + }) + }), + } + + cli.PopulateFlagSet(cfg, connectCmd.Flags(), vpr) + return connectCmd +} + func NewRootCmd(cfg *config.Config) *cobra.Command { vpr := newViper() rootCmd := &cobra.Command{ diff --git a/pkg/app/config/config.go b/pkg/app/config/config.go index fdee559..e55b6e7 100644 --- a/pkg/app/config/config.go +++ b/pkg/app/config/config.go @@ -11,6 +11,7 @@ type Config struct { OFFCPU OFFCPU `skip:"true" mapstructure:",squash"` FUTEXSNOOP FUTEXSNOOP `skip:"true" mapstructure:",squash"` SYSCALL SYSCALL `skip:"true" mapstructure:",squash"` + CACHESTAT CACHESTAT `skip:"true" mapstructure:",squash"` } type FUTEXSNOOP struct { @@ -77,6 +78,17 @@ type ONCPU struct { Exporter string `def:"" desc:"data exporter: loki,pyroscoe,prometheus,disk,etc." mapstructure:"exporter"` } +type CACHESTAT struct { + LogLevel string `def:"info" desc:"log level: debug|info|warn|error" mapstructure:"log-level"` + AppName string `def:"" desc:"application name used when uploading profiling data" mapstructure:"app-name"` + Server string `def:"http://localhost:4040" desc:"the server address" mapstructure:"server"` + UploadRate time.Duration `def:"30s" desc:"upload for the cachestat data. 30 means 30s upload the trace data from kernel" mapstructure:"upload-rate"` + Pid int `def:"-1" desc:"pid to trace, -1 to trace all pids" mapstructure:"pid"` + CacheType int `def:"0" desc:"cache type to trace, 0:read/write cache, 1:read cache, 2:write cache" mapstructure:"cache-type"` + BtfPath string `def:"" desc:"btf file path" mapstructure:"btf-path"` + Exporter string `def:"" desc:"data exporter: loki,pyroscoe,prometheus,disk,etc." mapstructure:"exporter"` +} + type NET struct { LogLevel string `def:"info" desc:"log level: debug|info|warn|error" mapstructure:"log-level"` diff --git a/pkg/component/detector/memdetector/memdetector.go b/pkg/component/detector/memdetector/memdetector.go index 83e1999..5c0d713 100644 --- a/pkg/component/detector/memdetector/memdetector.go +++ b/pkg/component/detector/memdetector/memdetector.go @@ -45,6 +45,7 @@ func (mem *MemDetector) Start() error { } func (mem *MemDetector) Stop() error { + close(mem.stopChan) return nil } @@ -52,7 +53,55 @@ func (mem *MemDetector) Name() string { return DetectorMemType } +func (mem *MemDetector) formatCacheStat(e *model.SpyEvent) (*model.AttributeMap, error) { + labels := model.NewAttributeMap() + for i := 0; i < int(e.ParamsCnt); i++ { + userAttributes := e.UserAttributes[i] + switch { + case userAttributes.GetKey() == "read_size_m": + labels.AddIntValue(model.ReadSizeM, int64(userAttributes.GetUintValue())) + case userAttributes.GetKey() == "write_size_m": + labels.AddIntValue(model.WriteSizeM, int64(userAttributes.GetUintValue())) + case userAttributes.GetKey() == "pid": + labels.AddIntValue(model.Pid, int64(userAttributes.GetUintValue())) + case userAttributes.GetKey() == "comm": + labels.AddStringValue(model.Comm, string(userAttributes.GetValue())) + case userAttributes.GetKey() == "file": + labels.AddStringValue(model.File, string(userAttributes.GetValue())) + } + } + return labels, nil +} + +func (mem *MemDetector) cachestatHandler(e *model.SpyEvent) (*model.DataBlock, error) { + labels, _ := mem.formatCacheStat(e) + val := e.GetUintUserAttribute("write_size") + metric := model.NewIntMetric(model.CacheStatMetricName, int64(val)) + return model.NewDataBlock(model.CacheStat, labels, e.TimeStamp, metric), nil +} func (mem *MemDetector) ProcessEvent(e *model.SpyEvent) error { + var dataBlock *model.DataBlock + var err error + switch e.Name { + case model.CacheStat: + dataBlock, err = mem.cachestatHandler(e) + default: + return nil + } + if err != nil { + return nil + } + if dataBlock == nil { + return nil + } + // next consumer is default processor + for _, con := range mem.consumers { + err := con.Consume(dataBlock) + if err != nil { + log.Loger.Error("consumer consume event failed:%v", err) + return err + } + } return nil } @@ -72,11 +121,12 @@ func (mem *MemDetector) ConsumeChanEvents() { } // 公共接口 -func (mem *MemDetector) ConsumeEvent(*model.SpyEvent) error { +func (mem *MemDetector) ConsumeEvent(e *model.SpyEvent) error { + mem.eventChan <- e return nil } // hard code func (mem *MemDetector) OwnedEvents() []string { - return []string{} + return []string{model.CacheStat} } diff --git a/pkg/component/processor/processor.go b/pkg/component/processor/processor.go index eb4185b..069337c 100644 --- a/pkg/component/processor/processor.go +++ b/pkg/component/processor/processor.go @@ -156,6 +156,8 @@ func (d *DefaultProcessor) Consume(data *model.DataBlock) error { case model.Syscall: fallthrough case model.FutexSnoop: + fallthrough + case model.CacheStat: err := d.consumer.Consume(data) if err != nil { log.Loger.Error("exporter consume data failed:%v", err) diff --git a/pkg/core/model/const.go b/pkg/core/model/const.go index 372ba15..ebda6a8 100644 --- a/pkg/core/model/const.go +++ b/pkg/core/model/const.go @@ -15,6 +15,7 @@ const ( OnCpu = "oncpu" FutexSnoop = "futexsnoop" Syscall = "syscall" + CacheStat = "cachestat" OtherEvent = "other_event" ) @@ -22,6 +23,7 @@ const ( const ( OffCpuMetricName = "offcpu_dur_ms" FutexMaxUerCountName = "max_futex_user_cnt" + CacheStatMetricName = "cachestat_read_size" ) // for labels @@ -73,6 +75,11 @@ const ( // for syscall DurMs = "dur_ms" DurUs = "dur_us" + + // for cachestat + ReadSizeM = "read_size_m" + WriteSizeM = "write_size_m" + File = "file" ) const ( diff --git a/pkg/ebpf/mem/cachestat.go b/pkg/ebpf/mem/cachestat.go new file mode 100644 index 0000000..bd6626d --- /dev/null +++ b/pkg/ebpf/mem/cachestat.go @@ -0,0 +1,246 @@ +package mem + +import ( + "bytes" + _ "embed" + "encoding/binary" + "fmt" + "sync" + "time" + "unsafe" + + bpf "github.com/aquasecurity/libbpfgo" + "github.com/chentao-kernel/spycat/pkg/app/config" + "github.com/chentao-kernel/spycat/pkg/core" + "github.com/chentao-kernel/spycat/pkg/core/model" + "github.com/chentao-kernel/spycat/pkg/log" + "github.com/chentao-kernel/spycat/pkg/util" + "golang.org/x/sys/unix" +) + +// #cgo CFLAGS: -I./cachestat/ +// #include +// #include "cachestat.h" +import "C" + +type CacheStat struct { + Pid uint32 + pad uint32 + Comm [16]byte + File [64]byte + ReadSize uint64 + WriteSize uint64 +} + +type CacheStatArgs struct { + Pid uint32 + CacheType uint32 +} + +type CacheStatSession struct { + Session *core.Session + Module *bpf.Module + mapCacheStat *bpf.BPFMap + Args CacheStatArgs + ticker *time.Ticker + BtfPath string + modMutex sync.Mutex +} + +func NewCacheStatBpfSession(name string, cfg *config.CACHESTAT, buf chan *model.SpyEvent) core.BpfSpyer { + return &CacheStatSession{ + Session: core.NewSession(name, &core.SessionConfig{}, buf), + Args: CacheStatArgs{ + Pid: uint32(cfg.Pid), + CacheType: uint32(cfg.CacheType), + }, + BtfPath: cfg.BtfPath, + ticker: time.NewTicker(cfg.UploadRate), + } +} + +func (b *CacheStatSession) attachProgs() error { + progIter := b.Module.Iterator() + for { + prog := progIter.NextProgram() + if prog == nil { + break + } + if _, err := prog.AttachGeneric(); err != nil { + return err + } + fmt.Printf("cachestat prog:%s attach success\n", prog.GetName()) + } + return nil +} + +func (b *CacheStatSession) initArgsMap() error { + var id uint32 + args := &CacheStatArgs{ + Pid: b.Args.Pid, + CacheType: b.Args.CacheType, + } + maps, err := b.Module.GetMap("args_map") + if err != nil { + return fmt.Errorf("get args_map failed:%v", err) + } + maps.Update(unsafe.Pointer(&id), unsafe.Pointer(args)) + fmt.Printf("update user_args success\n") + util.PrintStructFields(b.Args) + return nil +} + +// revive:disable:function-result-limit +func (b *CacheStatSession) getCacheStatMapValues() (keys [][]byte, values [][]byte, batch bool, err error) { + var ( + mapSize = C.CACHESTAT_MAP_SIZE + keySize = int(unsafe.Sizeof(C.struct_cache_key{})) + allKeys = make([]byte, mapSize*keySize) + pKeys = unsafe.Pointer(&allKeys[0]) + nextKey = C.struct_cache_key{} + ) + values, err = b.mapCacheStat.GetValueBatch(pKeys, nil, unsafe.Pointer(&nextKey), uint32(mapSize)) + if len(values) > 0 { + keys = collectBatchValues(allKeys, len(values), keySize) + return keys, values, true, nil + } + // try iterating + it := b.mapCacheStat.Iterator() + for it.Next() { + key := it.Key() + v, err := b.mapCacheStat.GetValue(unsafe.Pointer(&key[0])) + if err != nil { + return nil, nil, false, err + } + keyCopy := make([]byte, len(key)) // The slice is valid only until the next call to Next. + copy(keyCopy, key) + keys = append(keys, keyCopy) + values = append(values, v) + } + return keys, values, false, nil +} + +func (b *CacheStatSession) Reset() error { + _, values, _, err := b.getCacheStatMapValues() + if err != nil { + return err + } + + for _, value := range values { + v := (*C.struct_cache_info)(unsafe.Pointer(&value[0])) + pid := uint32(v.pid) + comm := C.GoString(&v.comm[0]) + file := C.GoString(&v.file[0]) + read_size := uint64(v.read_size) + write_size := uint64(v.write_size) + + event := &model.SpyEvent{} + event.Name = model.CacheStat + event.Class.Event = model.CacheStat + event.TimeStamp = uint64(time.Now().Unix()) + + event.SetUserAttributeWithByteBuf("comm", []byte(comm)) + event.SetUserAttributeWithByteBuf("file", []byte(file)) + event.SetUserAttributeWithUint32("pid", pid) + event.SetUserAttributeWithUint64("read_size_m", read_size) + event.SetUserAttributeWithUint64("write_size_m", write_size) + // fmt.Printf("pid:%d,comm:%s,file:%s,read_size:%d,write_size:%d\n", pid, comm, file, read_size, write_size) + b.Session.DataBuffer <- event + } + return nil +} + +func (b *CacheStatSession) PollData() { + for { + select { + case <-b.ticker.C: + b.Reset() + } + } +} + +func (b *CacheStatSession) Start() error { + var err error + + fmt.Println("cachestat start trace") + if err = unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{ + Cur: unix.RLIM_INFINITY, + Max: unix.RLIM_INFINITY, + }); err != nil { + return err + } + + b.modMutex.Lock() + defer b.modMutex.Unlock() + + args := bpf.NewModuleArgs{ + BPFObjBuff: cachestatBpf, + BTFObjPath: b.BtfPath, + } + + if b.Module, err = bpf.NewModuleFromBufferArgs(args); err != nil { + return err + } + if err = b.Module.BPFLoadObject(); err != nil { + return err + } + + err = b.initArgsMap() + if err != nil { + return fmt.Errorf("init args map failed:%v", err) + } + + b.mapCacheStat, err = b.Module.GetMap("cachestat_map") + if err != nil { + return fmt.Errorf("cachestat get cachestat map failed:%v", err) + } + + err = b.attachProgs() + if err != nil { + return fmt.Errorf("cachestat attach program failed:%v", err) + } + + b.PollData() + return nil +} + +func (b *CacheStatSession) HandleEvent(data []byte) { + var event CacheStat + + spyEvent := &model.SpyEvent{} + if err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &event); err != nil { + log.Loger.Error("cachestat parse event: %s", err) + } + b.Session.DataBuffer <- spyEvent +} +func (b *CacheStatSession) Stop() error { + if b.Module != nil { + b.Module.Close() + } + return nil +} + +func (b *CacheStatSession) ReadEvent() error { + return nil +} + +func (b *CacheStatSession) ConsumeEvent() error { + return nil +} + +func (b *CacheStatSession) Name() string { + return b.Session.Name() +} + +func collectBatchValues(values []byte, count int, valueSize int) [][]byte { + var value []byte + var collected [][]byte + for i := 0; i < count*valueSize; i += valueSize { + value = values[i : i+valueSize] + collected = append(collected, value) + } + return collected +} + +//go:embed cachestat/cachestat.bpf.o +var cachestatBpf []byte diff --git a/pkg/ebpf/mem/cachestat/cachestat.bpf.c b/pkg/ebpf/mem/cachestat/cachestat.bpf.c new file mode 100644 index 0000000..d9e9b30 --- /dev/null +++ b/pkg/ebpf/mem/cachestat/cachestat.bpf.c @@ -0,0 +1,122 @@ +/* + * Copyright: Copyright (c) Tao Chen + * Author: Tao Chen + */ + +#include +#include +#include +#include + +/* https://github.com/iovisor/bcc/issues/237 */ + +#include "cachestat.h" + +#define CACHESTAT_MAX_ENTRIES CACHESTAT_MAP_SIZE + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, u32); + __type(value, struct user_args); + __uint(max_entries, 8); +} args_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, struct cache_key); + __type(value, struct cache_info); + __uint(max_entries, CACHESTAT_MAX_ENTRIES); +} cachestat_map SEC(".maps"); + +SEC("kprobe/vfs_write") +int vfs_write_hook(struct pt_regs *ctx) +{ + struct cache_key key = { 0 }; + struct cache_info cache_info = { 0 }; + struct cache_info *info = NULL; + struct file *f = NULL; + struct dentry *de = NULL; + struct qstr dn = { 0 }; + struct user_args *args = NULL; + int count, index = 0; + u64 pid_tgid = bpf_get_current_pid_tgid(); + key.pid = pid_tgid >> 32; + args = bpf_map_lookup_elem(&args_map, &index); + if (!args) + return 0; + if (args->pid != -1 && args->pid != key.pid) { + return 0; + } + if (args->cache_type == READ_CACHE) + return 0; + + f = (struct file *)PT_REGS_PARM1_CORE(ctx); + de = (struct dentry *)BPF_CORE_READ(f, f_path.dentry); + dn = BPF_CORE_READ(de, d_name); + count = (int)PT_REGS_PARM3_CORE(ctx); + + key.file = dn.name; + info = bpf_map_lookup_elem(&cachestat_map, &key); + if (!info) { + cache_info.pid = key.pid; + bpf_get_current_comm(cache_info.comm, sizeof(cache_info.comm)); + if (dn.name != NULL) + bpf_core_read(&cache_info.file, sizeof(cache_info.file), + dn.name); + else + cache_info.file[0] = '?'; + cache_info.write_size = count; + bpf_map_update_elem(&cachestat_map, &key, &cache_info, 0); + return 0; + } + info->write_size = info->write_size + count; + return 0; +} + +SEC("kprobe/vfs_read") +int vfs_read_hook(struct pt_regs *ctx) +{ + struct cache_key key = { 0 }; + struct cache_info cache_info = { 0 }; + struct cache_info *info = NULL; + struct file *f = NULL; + struct dentry *de = NULL; + struct qstr dn = { 0 }; + struct user_args *args = NULL; + int count, index = 0; + u64 pid_tgid = bpf_get_current_pid_tgid(); + key.pid = pid_tgid >> 32; + + args = bpf_map_lookup_elem(&args_map, &index); + if (!args) + return 0; + if (args->pid != -1 && args->pid != key.pid) + return 0; + if (args->cache_type == WRITE_CACHE) + return 0; + + f = (struct file *)PT_REGS_PARM1_CORE(ctx); + de = (struct dentry *)BPF_CORE_READ(f, f_path.dentry); + dn = BPF_CORE_READ(de, d_name); + count = (int)PT_REGS_PARM3_CORE(ctx); + + key.file = dn.name; + info = bpf_map_lookup_elem(&cachestat_map, &key); + if (!info) { + cache_info.pid = key.pid; + bpf_get_current_comm(cache_info.comm, sizeof(cache_info.comm)); + if (dn.name != NULL) + bpf_core_read(&cache_info.file, sizeof(cache_info.file), + dn.name); + else + cache_info.file[0] = '?'; + cache_info.read_size = count; + bpf_map_update_elem(&cachestat_map, &key, &cache_info, 0); + return 0; + } + info->read_size = info->read_size + count; + + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/pkg/ebpf/mem/cachestat/cachestat.h b/pkg/ebpf/mem/cachestat/cachestat.h new file mode 100644 index 0000000..7602333 --- /dev/null +++ b/pkg/ebpf/mem/cachestat/cachestat.h @@ -0,0 +1,55 @@ +/* + * Copyright: Copyright (c) Tao Chen + * Author: Tao Chen + */ + +#ifndef __CACHESTAT_H +#define __CACHESTAT_H + +#define TASK_COMM_LEN 16 +#define CACHE_FILE_SIZE 64 +#define CACHESTAT_MAP_SIZE 10240 + +typedef signed char __s8; +typedef unsigned char __u8; +typedef short int __s16; +typedef short unsigned int __u16; +typedef int __s32; +typedef unsigned int __u32; +typedef long long int __s64; +typedef long long unsigned int __u64; +typedef __s8 s8; +typedef __u8 u8; +typedef __s16 s16; +typedef __u16 u16; +typedef __s32 s32; +typedef __u32 u32; +typedef __s64 s64; +typedef __u64 u64; + +/* TODO: */ +struct user_args { + __u32 pid; + __u32 cache_type; +}; + +enum CACHE_TYPE { + READ_WRITE_CACHE = 0, + READ_CACHE = 1, + WRITE_CACHE = 2, +}; +struct cache_key { + __u64 pid; + const unsigned char *file; +}; + +struct cache_info { + __u32 pid; + __u32 pad; + char comm[TASK_COMM_LEN]; + char file[CACHE_FILE_SIZE]; + __u64 read_size; + __u64 write_size; +}; + +#endif // !__CACHESTAT_H diff --git a/test/pagecache.py b/test/pagecache.py new file mode 100644 index 0000000..986b815 --- /dev/null +++ b/test/pagecache.py @@ -0,0 +1,33 @@ +# Copyright: Copyright (c) Tao Chen +# Author: Tao Chen +# Time: 2024-11-20 12:01:59 +import os +import time + +def create_write_cache(file_path, size_mb): + # Convert size from MB to bytes + size_bytes = size_mb * 1024 * 1024 + # Generate a block of data to write + data_block = b'0' * 1024 * 1024 # 1MB block of zeros + + with open(file_path, 'wb') as f: + for _ in range(size_mb): + f.write(data_block) + + print(f"{size_mb}MB file created at {file_path}") + #time.sleep(20) + +def create_read_cache(file_path, size_mb): + with open(file_path, 'rb') as f: + while f.read(1024 * 1024): + pass + print(f"{size_mb}MB file created from {file_path}") + + +def main(): + file_path = 'large_file.dat' + size_mb = 200 + create_write_cache(file_path, size_mb) + +if __name__ == '__main__': + main()