[CSE231 Paper Reading] The UNIX fast file system

Motivation

  1. The original UNIX operating system was FFS's forerunner and was straightforward and user-friendly. But there were some serious issues with it:
  • Poor performance was the central issue: The disk bandwidth utilization in some circumstances, as measured in this paper, was only 3%. Moreover, the throughput was poor.
  • Low locality was the cause of this: Inodes and data blocks were dispersed around the disk because the original UNIX file system viewed the disk as a random-access memory.
  • Fragmentation of the file system was another issue due to careless management of the available space. The file system became fragmented as more and more data came in and went out; as a result, the free list eventually pointed to blocks scattered around the disk, making it challenging to access logically contiguous files without repeatedly traversing the disk. Tools for disk defragmentation initially provided a solution to this issue.
  • A more petite size was excellent for eliminating internal fragmentation (waste within the block) but poor for transfers because each block required a placement overhead. The original block size was too small (512 bytes).

Solution

FFS guarantees that accessing two files simultaneously won't necessitate extensive disk search times by grouping

  • The superblock's location is rotated, replicated, and stored in several cylinder groups. This lessens the possibility of data loss as a result of superblock corruption.

Layout guidelines

Simple is the basic mantra: Keep similar things close together and irrelevant things separate.

  • FFS looks for the cylinder group with the freest inodes and the fewest allocated directories to place directories.
  • Setting up files: A file's data blocks are first allocated to the same group as its inode. In the same cylinder group as other files within the same directory, file inodes are also given.
  • A file system block now has a 4096-byte size instead of the previous 512-byte size (4 KB). This raises performance due to
  • More data is accessed with each disk transfer.
  • It is possible to describe more files without having to visit indirect blocks (since the direct blocks now contain more data)

Block Size

A file system block now has a 4096-byte size instead of the previous 512-byte size (4 KB). This raises performance due to more data being accessed with each disk transfer. It is possible to describe more files without having to visit indirect blocks (since the direct blocks now contain more data)

Experiments

Table 2b shows the reads and writes are around 3% to 47% faster in disk bandwidth.

Besides, FFS provided specific functionality improvements to the file system that have become standard in modern operating systems and will probably help FFS expand its user base:

  1. Long file names: File names are now almost limitless in length (previously: 8 characters. now: 255 characters)
  2. Programs can now lock files using advised shared or exclusive locks down to the individual file level.
  3. Symbolic links: Users now have considerably greater flexibility because they may construct "aliases" for any other file or directory on a system.
  4. Renaming files: Operation of atomic rename()
  5. Quotas: Limit the number of inodes and disk blocks a user may receive from the file system.
  6. Crash Consistency: FFS relies on the clean unmount of the filesystem to avoid consistency issues. In case of crash or power shortages, file system users have to invoke and wait for the lengthy file system consistency checker, fsck, which will detect consistency issues and attempt to recover without guarantee.

Evaluation effectiveness

I generally believe the paper since it adds up to 47% of the performance compared to the previous paper. I have a question for this paper 1. read-write lock performance compared with mandatory lock in different workloads 2. block wear leveling. 3. directory inode slow traverse. (here EXT4 fast start resolves this HERMES)

Novelty

Spreading file blocks on disk can hurt performance, especially when accessing files sequentially. But this problem can be solved by carefully choosing the block size. Specifically, if the block size is large enough, the file system will spend most of its time transferring data from disk and only (relatively) little time looking for data between blocks of blocks. This process of reducing overhead by increasing the amount of work per operation is called amortization and is a common technique in computer systems.

The second clever mechanism is a disk layout that is optimized for performance. The problem occurs when files are placed in contiguous sectors. Suppose FFS first reads block 0, and when the read is complete, requests the first block, but by this time, the head has already crossed over block 1, and if it wants to read block one, it must reevaluate again. FFS solves this problem with a different data layout. By spreading the data out, FFS has enough time to request the next block before the head crosses the block. FFS can calculate for different disks how many blocks should be jumped for placement to avoid additional spins; this technique is called parametrization. Such a layout gets only 50% of the peak bandwidth since you have to bypass each track twice to read each block once. Fortunately, modern disks are smarter: the disk reads the entire way as a cache, and then, for subsequent reads of the track, only the required data is returned from the store. So the file system doesn't have to worry about these low-level details.

SoTA connection

The latest paper, HTMFS in FAST 22, still cites this paper for discussing a clean fcsk. FFS has no consistency guarantee if a crash happens before a clean unmount.

EXT4, F2FS, and so many PM FSes are obviously the descendent of FFS. However, the current FS more focus on the performance of the software with regard to the fast media of storage CXL attached persistent memory. We've seen hacks in software stacks like mapping all the operations like writing combining operations to firmware or wraps into a fast data structure like ART. We see the SoTA FS stack more referring to LFS or PLFS rather than FFS in terms of journaling and firmware FS.

Reference

  1. HTMFS: Strong Consistency Comes for Free with Hardware TransactionalMemory in Persistent Memory File Systems
  2. https://ext4.wiki.kernel.org/index.php/Main_Page
  3. https://gavv.net/articles/file-locks/
  4. https://pages.cs.wisc.edu/~remzi/OSTEP/file-ffs.pdf

Scheduler Activations

For managing the concurrency using threads, we have 1. the user-level library to make management in the application's address space. + performant and flexible - less functional 2. kernel modification + high functionality - pool performance, flexibility. Thus we demand a kernel interface combined with a user-level thread package, which can gain the above 2 pros.

User-level Threads are usually lightweight, the current design of the fiber library, can predict the process well and squeeze the context switch to the full.

Cons

The upcall performance drains down the performance (a better message call interface in the kernel using eBPF(XDP for network) or io_uring to asynchronously submit the activation may resolve this problem.

Reference

  1. https://www.cs.ucr.edu/~heng/teaching/cs202-sp18/lec7.pdf

Lottery Scheduler

Motivation

deterministically fairly to give time tick to another process. Current scheduler designs like CFS and ULE's drawback 1. At best, priority schemes are ad hoc. Top ranking always prevails. A feedback loop is used to change priorities to attain "fair share" over the (very) long run 2. P priorities on Unix are dynamic. 3. Lower-priority t due to priority inversion. 4. Schedulers are intricate, and other tasks may stop high-priority tasking to debate.

Solution

A probabilistic process scheduling algorithm is lottery scheduling. Each process is given a small number of lottery tickets, and the scheduler holds a drawing to select a key at random. The process with the winning ticket is given the CPU. The distribution of the ticket keys could not be uniform. A process has a greater chance of winning and being chosen for execution if it has more tickets than the competition. The problem of famine is resolved, and probabilistic fairness is assured because a process with at least one ticket key will ultimately win a lottery. The distribution and allocation mechanism utilized determines the system's throughput and responsiveness.

Ticket transfers: This technique is used when a client is held up while holding a resource due to some dependencies while another client is waiting for the first client to release the shared resource. The concept behind this is to shift the tickets to the blocked client to be more likely to execute, accomplish the task faster, and release the resource sooner to benefit both clients. Any client they depend on must be able to transfer their tickets to one or more other clients.

Ticket Inflation: The Cost of Tickets A client can produce new tickets for themselves in this case, which is seen as an alternative to direct ticket transfers. On the one hand, this strategy may cause issues because a client may monopolize a resource by printing several lottery tickets. However, inflation/deflation can be utilized to modify resource allocations without explicit client-to-client communication, resulting in higher throughput. Tickets for Compensation If a client only consumes a portion f of the resource unit allotted to it (for instance, CPU cycles/time), it may be given compensation that increases the number of tickets it receives by a proportion of 1/f. This guarantees equity and improved responsiveness when it comes to process scheduling. Interactive processes commonly show this behavior.

Experiment for Significance

They try to prove the effectiveness of fairness and flexible control for multiple resources to contend. They also discussed the concurrency control, but they didn't bring up the searching for ticket time which is O(log n)

Experiment Fallacy

The Monte Carlo experiment provides the dynamic control feature but not giving a good idea of the inner functionality's percentage. I think compensation tickets are unnecessary because, in the long term, the odds should be balanced between the clients. Because we have to keep track of how many compensation tickets each client has, there is a cost associated with this. On the other hand, I believe this is a good illustration of the fresh possibilities the system's dynamic presents.
Also, the graphs were unreadable, and it would have been better to use normalized charts.

Novelty

The novelty is it describes a different scheduling system while you study this document regarding time-sharing tools. Probably the most straightforward scheduling algorithm is round-robin. It just keeps the resource occupied and has a high client throughput. But it's too easy. It does not offer the system any control over how much of the resourcehelp is allocated to each client. To address this, multilevel queue scheduling is used. The user has the option to group customers and assigns each group a distinct priority. We get control in exchange for new issues like priority inversion and famine. To address this issue further, mechanisms like priority inheritance and aging have been included. The scheduling algorithms have become more complex and challenging to comprehend.

Comment

It's a good approach, but not fast in today's operating system because of the complexity. If you write a distributed system, you definitely will use Round Robin + Consistent Hash and will not consider the scheduling with the nonconstant algorithm. But in other user applications, this scheduling will take place.
Also, we need to consider other metrics like the workload and context switch time to decide whether we need to use this, like implementing CFS/REAL_TIMERT/lottery and fallback to the different schedulers by different workloads.

Reference

  1. https://www.pdl.cmu.edu/PDL-FTP/Scheduling/lottery.pdf
  2. https://people.eecs.berkeley.edu/~brewer/cs262/Lec-scheduling.pdf

ESESC Project Proposal

CSE220的project是写个模拟器,暂时想写的是(类承影的 RVV GPU SM subsystem。) 没空了,不写了.这个emulated和sniper最大的区别是这个模拟器是timesampling based,精度会高很多。

project organization

    qemuStarted     = true;
    QEMUArgs *qdata = (struct QEMUArgs *)threadargs;

    int    qargc = qdata->qargc;
    char **qargv = qdata->qargv;

    MSG("Starting qemu with");
    for(int i = 0; i < qargc; i++)
      MSG("arg[%d] is: %s", i, qargv[i]);

    qemuesesc_main(qargc, qargv, NULL);

    MSG("qemu done");

    QEMUReader_finish(0);

    pthread_exit(0);

上层先走qemu先写了个bootloader,拿到Physical memory(其在qemu里插了trace记录memory。qemu 走完了(所以这里为毛不用JIT,可能是当时开发的局限吧。然后这边接一个指令parser。

模拟器进入循环后,会从qemu(libemul)中收集timing插桩信息。

  // 4 possible states:
  // rabbit  : no timing or warmup, go as fast as possible skipping stuff
  // warmup  : no timing but it has system warmup (caches/bpred)
  // detail  : detailed modeling, but without updating statistics
  // timing  : full timing modeling

另一个thread会运行下面的模型,初始化到memorysystem会从conf里面读cache的信息

-exec bt
#0  CCache::CCache (this=0x555558ae4360, gms=0x555558ae4280, section=0x555558ae42c0 "IL1_core", name=0x555558ae4340 "IL1(0)") at /home/victoryang00/Documents/esesc/simu/libmem/CCache.cpp:78
#1  0x0000555555836ccd in MemorySystem::buildMemoryObj (this=0x555558ae4280, device_type=0x5555589fa820 "icache", dev_section=0x555558ae42c0 "IL1_core", dev_name=0x555558ae4340 "IL1(0)") at /home/victoryang00/Documents/esesc/simu/libmem/MemorySystem.cpp:61
#2  0x000055555586c99e in GMemorySystem::finishDeclareMemoryObj (this=0x555558ae4280, vPars=..., name_suffix=0x0) at /home/victoryang00/Documents/esesc/simu/libcore/GMemorySystem.cpp:320
#3  0x000055555586c497 in GMemorySystem::declareMemoryObj (this=0x555558ae4280, block=0x5555589ee260 "tradCORE", field=0x555555a7e573 "IL1") at /home/victoryang00/Documents/esesc/simu/libcore/GMemorySystem.cpp:242
#4  0x000055555586bb0e in GMemorySystem::buildMemorySystem (this=0x555558ae4280) at /home/victoryang00/Documents/esesc/simu/libcore/GMemorySystem.cpp:148
#5  0x000055555581c764 in BootLoader::createSimuInterface (section=0x5555589ee260 "tradCORE", i=0) at /home/victoryang00/Documents/esesc/simu/libsampler/BootLoader.cpp:281
#6  0x000055555581c1c4 in BootLoader::plugSimuInterfaces () at /home/victoryang00/Documents/esesc/simu/libsampler/BootLoader.cpp:219
#7  0x000055555581cba1 in BootLoader::plug (argc=1, argv=0x7fffffffd888) at /home/victoryang00/Documents/esesc/simu/libsampler/BootLoader.cpp:320
#8  0x000055555572274d in main (argc=1, argv=0x7fffffffd888) at /home/victoryang00/Documents/esesc/main/esesc.cpp:37

CPU Model

指令被qemu issue了以后会有个taskhandler接,prefetcher和address predictor 也实现在libcore里面.有LSQ和storeSet的实现,就是那个RMO的Load Store forward queue. OoO/prefetch实现也是最全的.

Memory Model

触发ld/st/mv等memory指令的时候会直接call memorysubsystem 核心内的L1/L2都会有MemObj记录现在的地址,自动机抽象成MemRequest,noblocking先查询MSHR,到了不同MemObj更新状态.到Memory侧有controller分配dim和Xbar分配load的路径.

GPU Model

这个走的是cuda模型,编译器会生成riscv host代码和gpu device代码,SM 会执行GPU代码,然后他们的SM流处理器记录的东西和CPU部分一样的metrics.

power emulation

用了一个pwr+McPAT的power model来完成libpeq的模拟,Component category可以模拟L1I等等,还有实时更新的McPT和Gstat的counter。只是现在暂时只有SRAM和cache的model,也没有row buffer的模拟和leakage的模拟,假装所有东西都是正确运行的。

Reference

  1. https://masc.soe.ucsc.edu/esesc/resources/4-memory.pdf
  2. https://masc.soe.ucsc.edu/esesc/resources/5-powermodel%20.pdf
  3. https://masc.soe.ucsc.edu/esesc/resources/6-gpu.pdf

Pointer Chase

远端(异构)内存+内存随机workload会产生交替执行;这样会导致数据依赖(pointer-chasing现象)和加载到缓存里的数据重用率不高。为了具体分析这两个因素对系统性能的影响,可以尝试分别针对L1 cache、L2 Cache、L3 cache、本地内存以及远端(异构)内存的顺序读。

Pointer Chasing 是一个可以被利用的technique来得到远端内存的下一个block或track free block。 inode就是最好的例子。

应用

  1. 最近多次在optane memory的RE writeup 中找到,说明MMU的信息可以通过ptr chaisng leverage.
  2. 可以利用数据依赖的特性,在handler里实现逻辑,比如transfer 数据结构,或者oom exception.

Reference

  1. https://www.ssrc.ucsc.edu/media/pubs/329b041d657e2c2225aa68fb33e72ecca157e6df.pdf
  2. https://arxiv.org/pdf/2204.03289.pdf

a novel way to read from tar volume

intro

some of the volume is right in the root folder of the tar, for example.

.
├── AUTHORS
├── boards
│   ├── arm64.gni
│   ├── as370.gni
│   ├── BUILD.gn
│   ├── c18.gni
│   ├── chromebook-x64.gni
│   ├── cleo.gni
│   ├── hikey960.gni
│   ├── kirin970.gni
│   ├── msm8998.gni
│   ├── msm8x53-som.gni
│   ├── mt8167s_ref.gni
│   ├── OWNERS
│   ├── qemu-arm64.gni
│   ├── qemu-x64.gni
│   ├── toulouse.gni
│   ├── vim2.gni
│   ├── vim3.gni
│   ├── vs680.gni
│   └── x64.gni
├── build
│   ├── banjo
│   │   ├── banjo.gni
│   │   ├── banjo_library.gni
│   │   ├── BUILD.gn
│   │   ├── gen_response_file.py
│   │   ├── gen_sdk_meta.py
│   │   └── toolchain.gni
│   ├── bind
│   │   └── bind.gni
│   ├── board.gni
│   ├── BUILD.gn
│   ├── build_id.gni
│   ├── c
│   │   ├── banjo_c.gni
│   │   ├── BUILD.gn
│   │   └── fidl_c.gni
│   ├── cat.sh
│   ├── cipd.gni
│   ├── cmake
│   │   ├── HostLinuxToolchain.cmake
│   │   ├── README.md
│   │   └── ToolchainCommon.cmake
│   ├── cmx
│   │   ├── block_deprecated_misc_storage.json
│   │   ├── block_deprecated_shell.json
│   │   ├── block_rootjob_svc.json
│   │   ├── block_rootresource_svc.json
│   │   ├── cmx.gni
│   │   ├── facets
│   │   │   └── module_facet_schema.json
│   │   ├── internal_allow_global_data.cmx
│   │   └── OWNERS
│   ├── compiled_action.gni
│   ├── config
│   │   ├── arm.gni
│   │   ├── BUILDCONFIG.gn
│   │   ├── BUILD.gn
│   │   ├── clang
│   │   │   └── clang.gni
│   │   ├── compiler.gni
│   │   ├── fuchsia
│   │   │   ├── BUILD.gn
│   │   │   ├── rules.gni
│   │   │   ├── sdk.gni
│   │   │   ├── zbi.gni
│   │   │   ├── zircon.gni
│   │   │   ├── zircon_images.gni
│   │   │   └── zircon_legacy_vars.gni
│   │   ├── host_byteorder.gni
│   │   ├── linux
│   │   │   └── BUILD.gn
│   │   ├── lto
│   │   │   ├── BUILD.gn
│   │   │   └── config.gni
│   │   ├── mac
│   │   │   ├── BUILD.gn
│   │   │   ├── mac_sdk.gni
│   │   │   └── package_framework.py
│   │   ├── OWNERS
│   │   ├── profile
│   │   │   └── BUILD.gn
│   │   ├── sanitizers
│   │   │   ├── asan_default_options.c
│   │   │   ├── BUILD.gn
│   │   │   ├── debugdata.cmx
│   │   │   └── ubsan_default_options.c
│   │   ├── scudo
│   │   │   ├── BUILD.gn
│   │   │   ├── scudo_default_options.c
│   │   │   └── scudo.gni
│   │   └── sysroot.gni
│   ├── config.gni
│   ├── cpp
│   │   ├── binaries.py
│   │   ├── BUILD.gn
│   │   ├── extract_imported_symbols.gni
│   │   ├── extract_imported_symbols.sh
│   │   ├── extract_public_symbols.gni
│   │   ├── extract_public_symbols.sh
│   │   ├── fidl_cpp.gni
│   │   ├── fidlmerge_cpp.gni
│   │   ├── gen_sdk_prebuilt_meta_file.py
│   │   ├── gen_sdk_sources_meta_file.py
│   │   ├── sdk_executable.gni
│   │   ├── sdk_shared_library.gni
│   │   ├── sdk_source_set.gni
│   │   ├── sdk_static_library.gni
│   │   ├── verify_imported_symbols.gni
│   │   ├── verify_imported_symbols.sh
│   │   ├── verify_pragma_once.gni
│   │   ├── verify_pragma_once.py
│   │   ├── verify_public_symbols.gni
│   │   ├── verify_public_symbols.sh
│   │   └── verify_runtime_deps.py
│   ├── dart
│   │   ├── BUILD.gn
│   │   ├── dart.gni
│   │   ├── dart_library.gni
│   │   ├── dart_remote_test.gni
│   │   ├── dart_tool.gni
│   │   ├── empty_pubspec.yaml
│   │   ├── fidl_dart.gni
│   │   ├── fidlmerge_dart.gni
│   │   ├── gen_analyzer_invocation.py
│   │   ├── gen_app_invocation.py
│   │   ├── gen_dart_test_invocation.py
│   │   ├── gen_dot_packages.py
│   │   ├── gen_remote_test_invocation.py
│   │   ├── gen_test_invocation.py
│   │   ├── group_tests.py
│   │   ├── label_to_package_name.py
│   │   ├── OWNERS
│   │   ├── run_analysis.py
│   │   ├── sdk
│   │   │   ├── detect_api_changes
│   │   │   │   ├── analysis_options.yaml
│   │   │   │   ├── bin
│   │   │   │   │   └── main.dart
│   │   │   │   ├── BUILD.gn
│   │   │   │   ├── lib
│   │   │   │   │   ├── analyze.dart
│   │   │   │   │   ├── diff.dart
│   │   │   │   │   └── src
│   │   │   │   │       └── visitor.dart
│   │   │   │   ├── pubspec.yaml
│   │   │   │   ├── schema.json
│   │   │   │   └── test
│   │   │   │       ├── analyze_test.dart
│   │   │   │       └── diff_test.dart
│   │   │   ├── gen_meta_file.py
│   │   │   └── sort_deps.py
│   │   ├── test.gni
│   │   ├── toolchain.gni
│   │   └── verify_sources.py
│   ├── development.key
│   ├── driver_package.gni
│   ├── fidl
│   │   ├── BUILD.gn
│   │   ├── fidl.gni
│   │   ├── fidl_library.gni
│   │   ├── gen_response_file.py
│   │   ├── gen_sdk_meta.py
│   │   ├── linting_exceptions.gni
│   │   ├── OWNERS
│   │   ├── run_and_gen_stamp.sh
│   │   ├── toolchain.gni
│   │   └── wireformat.gni
│   ├── fuchsia
│   │   └── sdk.gni
│   ├── Fuchsia.cmake
│   ├── fuzzing
│   │   ├── BUILD.gn
│   │   ├── fuzzer.gni
│   │   └── OWNERS
│   ├── gn
│   │   ├── BUILD.gn
│   │   ├── gen_persistent_log_config.py
│   │   ├── OWNERS
│   │   ├── unpack_build_id_archives.sh
│   │   └── write_package_json.py
│   ├── gn_helpers.py
│   ├── gn_run_binary.sh
│   ├── go
│   │   ├── BUILD.gn
│   │   ├── build.py
│   │   ├── fidl_go.gni
│   │   ├── gen_library_metadata.py
│   │   ├── go_binary.gni
│   │   ├── go_build.gni
│   │   ├── go_fuzzer.gni
│   │   ├── go_fuzzer_wrapper.go
│   │   ├── go_library.gni
│   │   ├── go_test.gni
│   │   ├── OWNERS
│   │   └── toolchain.gni
│   ├── gypi_to_gn.py
│   ├── host.gni
│   ├── images
│   │   ├── add_tag_to_manifest.sh
│   │   ├── args.gni
│   │   ├── assemble_system.gni
│   │   ├── boot.gni
│   │   ├── bringup
│   │   │   └── BUILD.gn
│   │   ├── BUILD.gn
│   │   ├── collect_blob_manifest.gni
│   │   ├── create-shell-commands.py
│   │   ├── custom_signing.gni
│   │   ├── dummy
│   │   │   └── example.txt
│   │   ├── efi_local_cmdline.txt
│   │   ├── elfinfo.py
│   │   ├── filesystem_limits.gni
│   │   ├── finalize_manifests.py
│   │   ├── format_filesystem_sizes.py
│   │   ├── fvm.gni
│   │   ├── generate_flash_script.sh
│   │   ├── guest
│   │   │   ├── BUILD.gn
│   │   │   └── guest_meta_package.json
│   │   ├── manifest_add_dest_prefix.sh
│   │   ├── manifest_content_expand.sh
│   │   ├── manifest.gni
│   │   ├── manifest_list_collect_unique_blobs.py
│   │   ├── manifest.py
│   │   ├── max_fvm_size.gni
│   │   ├── OWNERS
│   │   ├── pack-images.py
│   │   ├── pkgfs.gni
│   │   ├── recovery
│   │   │   └── BUILD.gn
│   │   ├── shell_commands.gni
│   │   ├── system_image_prime_meta_package.json
│   │   ├── system_meta_package.json
│   │   ├── ta.gni
│   │   ├── update_package.json
│   │   ├── update_prime_package.json
│   │   ├── variant.py
│   │   ├── vbmeta.gni
│   │   ├── zedboot
│   │   │   ├── BUILD.gn
│   │   │   ├── efi_cmdline.txt
│   │   │   └── zedboot_args.gni
│   │   ├── zircon
│   │   │   ├── bootsvc.gni
│   │   │   └── BUILD.gn
│   │   └── zxcrypt.gni
│   ├── info
│   │   ├── BUILD.gn
│   │   ├── gen-latest-commit-date.sh
│   │   └── info.gni
│   ├── __init__.py
│   ├── json
│   │   └── validate_json.gni
│   ├── mac
│   │   └── find_sdk.py
│   ├── make_map.py
│   ├── module_args
│   │   └── dart.gni
│   ├── OWNERS
│   ├── package
│   │   └── component.gni
│   ├── package.gni
│   ├── packages
│   │   ├── BUILD.gn
│   │   ├── OWNERS
│   │   ├── prebuilt_package.gni
│   │   ├── prebuilt_package.py
│   │   ├── prebuilt_test_manifest.gni
│   │   └── prebuilt_test_package.gni
│   ├── persist_logs.gni
│   ├── README.md
│   ├── rust
│   │   ├── banjo_rust.gni
│   │   ├── banjo_rust_library.gni
│   │   ├── BUILD.gn
│   │   ├── config.gni
│   │   ├── fidl_rust.gni
│   │   ├── fidl_rust_library.gni
│   │   ├── list_files_in_dir.py
│   │   ├── OWNERS
│   │   ├── rustc_binary.gni
│   │   ├── rustc_binary_sdk.gni
│   │   ├── rustc_cdylib.gni
│   │   ├── rustc_fuzzer.gni
│   │   ├── rustc_library.gni
│   │   ├── rustc_macro.gni
│   │   ├── rustc_staticlib.gni
│   │   ├── rustc_test.gni
│   │   ├── stamp.sh
│   │   └── toolchain.gni
│   ├── sdk
│   │   ├── BUILD.gn
│   │   ├── compute_atom_api.py
│   │   ├── config.gni
│   │   ├── create_atom_manifest.py
│   │   ├── create_molecule_manifest.py
│   │   ├── export_sdk.py
│   │   ├── generate_archive_manifest.py
│   │   ├── generate_meta.py
│   │   ├── manifest_schema.json
│   │   ├── merged_sdk.gni
│   │   ├── meta
│   │   │   ├── banjo_library.json
│   │   │   ├── BUILD.gn
│   │   │   ├── cc_prebuilt_library.json
│   │   │   ├── cc_source_library.json
│   │   │   ├── common.json
│   │   │   ├── dart_library.json
│   │   │   ├── device_profile.json
│   │   │   ├── documentation.json
│   │   │   ├── fidl_library.json
│   │   │   ├── host_tool.json
│   │   │   ├── loadable_module.json
│   │   │   ├── manifest.json
│   │   │   ├── README.md
│   │   │   ├── src
│   │   │   │   ├── banjo_library.rs
│   │   │   │   ├── cc_prebuilt_library

conclusion

tar tjvf {}.bz2 | grep ^d  | awk -F/  '{if(NF<4) print }'

insights

  1. NF in awk is the number of fields divided by '/'! Not the number of '/'!
  2. After the '/' at the end of the line, even if there are no more characters, it is then counted into a field!
  3. For example, the following: drwxr-xr-x root / root 0 2011-08-26 09:18 bin / This is 3 fields! !! !!