From d11862a3ac0a1fde407a569e2f633fb58da46e6f Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Mon, 2 Aug 2021 16:21:54 +0200 Subject: [PATCH 1/5] move duplicated io_uring code into io_uring.h --- common.h | 1 + io-uring-no-syscall.c | 24 ++++++------------------ io-uring-sqpoll.c | 27 +++++++-------------------- io_uring.h | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 38 deletions(-) create mode 100644 io_uring.h diff --git a/common.h b/common.h index 80360f2..ade483f 100644 --- a/common.h +++ b/common.h @@ -1,5 +1,6 @@ #pragma once +#define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) extern size_t warmup, iterations; diff --git a/io-uring-no-syscall.c b/io-uring-no-syscall.c index 62f7c5a..9b0b014 100644 --- a/io-uring-no-syscall.c +++ b/io-uring-no-syscall.c @@ -4,26 +4,18 @@ #include #include "common.h" +#include "io_uring.h" #include "stopwatch.h" struct io_uring ring; -void init(__attribute__((unused)) int fd) { - int res = io_uring_queue_init(16, &ring, IORING_SETUP_SQPOLL); - if (res < 0) { - errno = res; - err(EXIT_FAILURE, "io_uring_setup failed"); - } -} +void init(__attribute__((unused)) int fd) { io_uring_init_sqpoll(&ring); } void do_write(int fd, const void *buf, size_t count) { - start_watch(); - struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); - while (unlikely(!sqe)) { - sqe = io_uring_get_sqe(&ring); - } + struct io_uring_sqe *sqe = sqpoll_ring_get_sqe(&ring); io_uring_prep_write(sqe, fd, buf, count, 0); + start_watch(); int res = io_uring_submit(&ring); struct io_uring_cqe *cqe; @@ -32,11 +24,7 @@ void do_write(int fd, const void *buf, size_t count) { stop_watch(); - if (res < 0) { - err(EXIT_FAILURE, "io_submit failed"); - } + if (res < 0) err(EXIT_FAILURE, "io_submit failed"); - if (cqe->res < 0) { - err(EXIT_FAILURE, "write request failed"); - } + if (cqe->res < 0) err(EXIT_FAILURE, "write request failed"); } diff --git a/io-uring-sqpoll.c b/io-uring-sqpoll.c index 349f71f..6fc2a9b 100644 --- a/io-uring-sqpoll.c +++ b/io-uring-sqpoll.c @@ -4,29 +4,19 @@ #include #include "common.h" +#include "io_uring.h" #include "stopwatch.h" struct io_uring ring; -void init(__attribute__((unused)) int fd) { - int res = io_uring_queue_init(16, &ring, IORING_SETUP_SQPOLL); - if (res < 0) { - errno = res; - err(EXIT_FAILURE, "io_uring_setup failed"); - } -} +void init(__attribute__((unused)) int fd) { io_uring_init_sqpoll(&ring); } void do_write(int fd, const void *buf, size_t count) { - start_watch(); - - struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); - while (unlikely(!sqe)) { - sqe = io_uring_get_sqe(&ring); - } + struct io_uring_sqe *sqe = sqpoll_ring_get_sqe(&ring); io_uring_prep_write(sqe, fd, buf, count, 0); + start_watch(); int res = io_uring_submit_and_wait(&ring, 1); - stop_watch(); if (res < 0) { @@ -35,11 +25,8 @@ void do_write(int fd, const void *buf, size_t count) { struct io_uring_cqe *cqe; res = io_uring_peek_cqe(&ring, &cqe); - if (res < 0) { - err(EXIT_FAILURE, "io_uring_peek_cqe failed"); - } - if (cqe->res < 0) { - err(EXIT_FAILURE, "write request failed"); - } + if (res < 0) err(EXIT_FAILURE, "io_uring_peek_cqe failed"); + + if (cqe->res < 0) err(EXIT_FAILURE, "write request failed"); } diff --git a/io_uring.h b/io_uring.h new file mode 100644 index 0000000..961ef30 --- /dev/null +++ b/io_uring.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include "common.h" + +#define GET_SQE_ATTEMPTS 1000000 +struct io_uring_sqe* sqpoll_ring_get_sqe(struct io_uring* ring) { + struct io_uring_sqe* sqe; + size_t attempts = 0; + for (;;) { + sqe = io_uring_get_sqe(ring); + if (likely(sqe)) return sqe; + + ++attempts; + + if (attempts < GET_SQE_ATTEMPTS) continue; + + errx(EXIT_FAILURE, "failed to get sqe after %d", GET_SQE_ATTEMPTS); + } +} + +#define SQPOLL_RING_ENTRIES 16 + +void io_uring_init_sqpoll(struct io_uring* ring) { + int res = io_uring_queue_init(SQPOLL_RING_ENTRIES, ring, IORING_SETUP_SQPOLL); + if (res < 0) { + errno = res; + err(EXIT_FAILURE, "io_uring_setup failed"); + } +} -- GitLab From 35bf403ca019926727bc21ca57123d75bb4a0754 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Mon, 2 Aug 2021 16:22:35 +0200 Subject: [PATCH 2/5] don't pin our thread --- Makefile | 2 +- bench.c | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 9768467..21b4705 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ OBJ := $(addprefix bench-,$(SYSCALLS)) LDFLAGS := -luring -pthread -lrt -CFLAGS := -Werror -Wall -g -O3 -D_GNU_SOURCE +CFLAGS := -Werror -Wall -g -O3 # CFLAGS := -Werror -Wall -g -O0 .PHONY: all clean eval docker-eval check diff --git a/bench.c b/bench.c index 2a2494d..c87d66e 100644 --- a/bench.c +++ b/bench.c @@ -1,11 +1,9 @@ #include #include -#include #include #include #include #include -#include #include "stopwatch.h" @@ -25,22 +23,11 @@ static int create_eventfd() { return fd; } -static void set_cpu_affinity(int cpu) { - cpu_set_t set; - // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - CPU_ZERO(&set); - CPU_SET(cpu, &set); - if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) - err(EXIT_FAILURE, "sched_setaffinity failed"); -} - int main() { uint64_t cycles_sum = 0; uint64_t nanos_sum = 0; uint64_t write_buf = 1; - set_cpu_affinity(0); - int fd = create_eventfd(); init(fd); -- GitLab From 4e1cf6965a2eef795710a74fc42f327cabec1743 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Mon, 2 Aug 2021 17:06:44 +0200 Subject: [PATCH 3/5] implement descriptive stats reporting --- Makefile | 8 ++++--- bench.c | 37 ++++++++++++++++++++------------ stats.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ stats.h | 9 ++++++++ 4 files changed, 102 insertions(+), 16 deletions(-) create mode 100644 stats.c create mode 100644 stats.h diff --git a/Makefile b/Makefile index 21b4705..5e6ec9f 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ BENCH_MAIN := bench.c SYSCALLS := blocking io-uring io-uring-sqpoll io-uring-no-syscall epoll paio-sig paio-thrd +STATS ?= + OBJ := $(addprefix bench-,$(SYSCALLS)) -LDFLAGS := -luring -pthread -lrt +LDFLAGS := -luring -pthread -lrt -lm CFLAGS := -Werror -Wall -g -O3 # CFLAGS := -Werror -Wall -g -O0 @@ -11,7 +13,7 @@ CFLAGS := -Werror -Wall -g -O3 .PHONY: all clean eval docker-eval check eval: all - @for syscall in $(SYSCALLS); do echo -n "$$syscall " ; ./bench-$$syscall; done + @for syscall in $(SYSCALLS); do echo -n "$$syscall " ; ./bench-$$syscall $(STATS); done docker-eval: ./docker.sh make eval @@ -19,7 +21,7 @@ docker-eval: all: $(OBJ) define generateTargets -bench-$(1): $(1).c bench.c stopwatch.c | Makefile +bench-$(1): $(1).c bench.c stopwatch.c stats.c | Makefile $(CC) $(CFLAGS) -o $$@ $$^ $(LDFLAGS) endef diff --git a/bench.c b/bench.c index c87d66e..abe96b5 100644 --- a/bench.c +++ b/bench.c @@ -1,10 +1,13 @@ #include #include +#include #include #include #include +#include #include +#include "stats.h" #include "stopwatch.h" void init(int fd); @@ -23,9 +26,11 @@ static int create_eventfd() { return fd; } -int main() { - uint64_t cycles_sum = 0; - uint64_t nanos_sum = 0; +int main(int argc, char *argv[]) { + bool print_stats = false; + if (argc > 2 || (argc == 2 && !(print_stats = (strcmp(argv[1], "--stats") == 0)))) + errx(EXIT_SUCCESS, "Usage: %s [--stats]", argv[0]); + uint64_t write_buf = 1; int fd = create_eventfd(); @@ -34,24 +39,30 @@ int main() { const size_t exp_warmup = warmup; const size_t exp_iterations = iterations; + uint64_t *nanos = malloc(exp_iterations * sizeof(uint64_t)); + uint64_t *cycles = malloc(exp_iterations * sizeof(uint64_t)); + if (!nanos || !cycles) err(EXIT_FAILURE, "allocating memory for our results failed"); + if (exp_iterations == 0) errx(EXIT_FAILURE, "experiment must do at least one iteration"); for (size_t i = 0; i < exp_warmup; ++i) do_write(fd, &write_buf, sizeof(write_buf)); for (int64_t i = 1; i <= exp_iterations; ++i) { do_write(fd, &write_buf, sizeof(write_buf)); - if (__builtin_add_overflow(nanos_sum, clock_diff_nanos(), &nanos_sum)) - errx(EXIT_FAILURE, "nanos overflowed at %ld", i); - if (__builtin_add_overflow(cycles_sum, clock_diff_cycles(), &cycles_sum)) - errx(EXIT_FAILURE, "cycles overflowed at %ld", i); + nanos[i] = clock_diff_nanos(); + cycles[i] = clock_diff_cycles(); } - // Since uint64_t <-> double conversion are not well defined - // and we use really small units (cycles and nanoseconds) I am willing - // to accept that we throw away anything after the decimal point. - uint64_t avg_nanos = nanos_sum / exp_iterations; - uint64_t avg_cycles = cycles_sum / exp_iterations; + if (print_stats) { + print_desc_stats("nanos", nanos, exp_iterations); + print_desc_stats("cycles", cycles, exp_iterations); + } else { + uint64_t avg_nanos = calc_mean(nanos, exp_iterations); + uint64_t avg_cycles = calc_mean(cycles, exp_iterations); + printf("avg-nanos: %lu, avg-cycles: %lu\n", avg_nanos, avg_cycles); + } - printf("%lu ns, %lu cycles\n", avg_nanos, avg_cycles); + free(nanos); + free(cycles); return 0; } diff --git a/stats.c b/stats.c new file mode 100644 index 0000000..5ceaa9c --- /dev/null +++ b/stats.c @@ -0,0 +1,64 @@ +#include "stats.h" + +#include +#include +#include +#include + +uint64_t calc_median(uint64_t* data, size_t size) { + // sample size is odd -> there is a middle value + if (size % 2 == 1) return data[size / 2]; + + // sample size is even calculate a virtual middle value + const uint64_t before_median = data[size / 2]; + const uint64_t after_median = data[(size / 2) + 1]; + return before_median + ((after_median - before_median) / 2); +} + +uint64_t calc_mean(uint64_t* data, size_t size) { + uint64_t sum = 0; + for (size_t i = 0; i < size; ++i) { + if (__builtin_add_overflow(sum, data[i], &sum)) errx(EXIT_FAILURE, "sum overflowed at %ld", i); + } + return sum / size; +} + +uint64_t calc_var(uint64_t* data, size_t size, uint64_t mean) { + uint64_t sum = 0; + for (size_t i = 0; i < size; ++i) { + const int64_t delta = mean - data[i]; + const uint64_t delta_pow = (uint64_t)pow((double)delta, 2); + if (__builtin_add_overflow(sum, delta_pow, &sum)) + errx(EXIT_FAILURE, "sum overflowed at %ld", i); + } + return sum / size; +} + +static int compare_uint64_t(const void* v1, const void* v2) { + const uint64_t x1 = *(uint64_t*)v1; + const uint64_t x2 = *(uint64_t*)v2; + if (x1 < x2) return -1; + + if (x1 > x2) return 1; + + return 0; +} + +void print_desc_stats(const char* name, uint64_t* data, size_t size) { + // sort our data + qsort(data, size, sizeof(uint64_t), compare_uint64_t); + + const uint64_t min = data[0]; + const uint64_t max = data[size - 1]; + const uint64_t median = calc_median(data, size); + const uint64_t mean = calc_mean(data, size); + const uint64_t var = calc_var(data, size, mean); + const uint64_t std = (uint64_t)sqrt((double)var); + + printf("%s-min: %lu\n", name, min); + printf("%s-max: %lu\n", name, max); + printf("%s-median: %lu\n", name, median); + printf("%s-mean: %lu\n", name, mean); + printf("%s-var: %lu\n", name, var); + printf("%s-std: %lu\n", name, std); +} diff --git a/stats.h b/stats.h new file mode 100644 index 0000000..db45f46 --- /dev/null +++ b/stats.h @@ -0,0 +1,9 @@ +#pragma once + +#include "stdint.h" +#include "stdlib.h" + +uint64_t calc_mean(uint64_t* data, size_t size); +uint64_t calc_median(uint64_t* data, size_t size); +uint64_t calc_var(uint64_t* data, size_t size, uint64_t mean); +void print_desc_stats(const char* name, uint64_t* data, size_t size); \ No newline at end of file -- GitLab From de592221c6d0c649d65eca6106f9a6d2cc40d826 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Mon, 2 Aug 2021 17:26:55 +0200 Subject: [PATCH 4/5] improve descriptive stats --- Makefile | 10 ++++++++-- bench.c | 11 ++++++----- stats.c | 32 ++++++++++++++++++++------------ stats.h | 8 ++++---- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 5e6ec9f..5cd7361 100644 --- a/Makefile +++ b/Makefile @@ -10,14 +10,20 @@ LDFLAGS := -luring -pthread -lrt -lm CFLAGS := -Werror -Wall -g -O3 # CFLAGS := -Werror -Wall -g -O0 -.PHONY: all clean eval docker-eval check +.PHONY: all clean eval eval-stats docker-eval docker-eval-stats check eval: all - @for syscall in $(SYSCALLS); do echo -n "$$syscall " ; ./bench-$$syscall $(STATS); done + @for syscall in $(SYSCALLS); do ./bench-$$syscall $(STATS); done + +eval-stats: + @$(MAKE) STATS=--stats eval docker-eval: ./docker.sh make eval +docker-eval-stats: + ./docker.sh make eval-stats + all: $(OBJ) define generateTargets diff --git a/bench.c b/bench.c index abe96b5..a1d9ef8 100644 --- a/bench.c +++ b/bench.c @@ -49,17 +49,18 @@ int main(int argc, char *argv[]) { for (int64_t i = 1; i <= exp_iterations; ++i) { do_write(fd, &write_buf, sizeof(write_buf)); - nanos[i] = clock_diff_nanos(); - cycles[i] = clock_diff_cycles(); + nanos[i - 1] = clock_diff_nanos(); + cycles[i - 1] = clock_diff_cycles(); } if (print_stats) { - print_desc_stats("nanos", nanos, exp_iterations); - print_desc_stats("cycles", cycles, exp_iterations); + printf("%s:\n", argv[0]); + print_desc_stats("nanos", " ", nanos, exp_iterations); + print_desc_stats("cycles", " ", cycles, exp_iterations); } else { uint64_t avg_nanos = calc_mean(nanos, exp_iterations); uint64_t avg_cycles = calc_mean(cycles, exp_iterations); - printf("avg-nanos: %lu, avg-cycles: %lu\n", avg_nanos, avg_cycles); + printf("%s avg-nanos: %lu, avg-cycles: %lu\n", argv[0], avg_nanos, avg_cycles); } free(nanos); diff --git a/stats.c b/stats.c index 5ceaa9c..b7a621d 100644 --- a/stats.c +++ b/stats.c @@ -5,7 +5,7 @@ #include #include -uint64_t calc_median(uint64_t* data, size_t size) { +uint64_t calc_median(const uint64_t* data, size_t size) { // sample size is odd -> there is a middle value if (size % 2 == 1) return data[size / 2]; @@ -15,7 +15,12 @@ uint64_t calc_median(uint64_t* data, size_t size) { return before_median + ((after_median - before_median) / 2); } -uint64_t calc_mean(uint64_t* data, size_t size) { +static void check_saple_size(size_t size) { + if (size == 0) errx(EXIT_FAILURE, "data size must not be zero"); +} + +uint64_t calc_mean(const uint64_t* data, size_t size) { + check_saple_size(size); uint64_t sum = 0; for (size_t i = 0; i < size; ++i) { if (__builtin_add_overflow(sum, data[i], &sum)) errx(EXIT_FAILURE, "sum overflowed at %ld", i); @@ -23,10 +28,11 @@ uint64_t calc_mean(uint64_t* data, size_t size) { return sum / size; } -uint64_t calc_var(uint64_t* data, size_t size, uint64_t mean) { +uint64_t calc_var(const uint64_t* data, size_t size, uint64_t mean) { + check_saple_size(size); uint64_t sum = 0; for (size_t i = 0; i < size; ++i) { - const int64_t delta = mean - data[i]; + const uint64_t delta = mean - data[i]; const uint64_t delta_pow = (uint64_t)pow((double)delta, 2); if (__builtin_add_overflow(sum, delta_pow, &sum)) errx(EXIT_FAILURE, "sum overflowed at %ld", i); @@ -44,9 +50,10 @@ static int compare_uint64_t(const void* v1, const void* v2) { return 0; } -void print_desc_stats(const char* name, uint64_t* data, size_t size) { +void print_desc_stats(const char* name, const char* indentation, const uint64_t* data, + size_t size) { // sort our data - qsort(data, size, sizeof(uint64_t), compare_uint64_t); + qsort((void*)data, size, sizeof(uint64_t), compare_uint64_t); const uint64_t min = data[0]; const uint64_t max = data[size - 1]; @@ -55,10 +62,11 @@ void print_desc_stats(const char* name, uint64_t* data, size_t size) { const uint64_t var = calc_var(data, size, mean); const uint64_t std = (uint64_t)sqrt((double)var); - printf("%s-min: %lu\n", name, min); - printf("%s-max: %lu\n", name, max); - printf("%s-median: %lu\n", name, median); - printf("%s-mean: %lu\n", name, mean); - printf("%s-var: %lu\n", name, var); - printf("%s-std: %lu\n", name, std); + printf("%s%s\n", indentation, name); + printf("%s%smin: %lu\n", indentation, indentation, min); + printf("%s%smax: %lu\n", indentation, indentation, max); + printf("%s%smedian: %lu\n", indentation, indentation, median); + printf("%s%smean: %lu\n", indentation, indentation, mean); + printf("%s%svar: %lu\n", indentation, indentation, var); + printf("%s%sstd: %lu\n", indentation, indentation, std); } diff --git a/stats.h b/stats.h index db45f46..8c210ce 100644 --- a/stats.h +++ b/stats.h @@ -3,7 +3,7 @@ #include "stdint.h" #include "stdlib.h" -uint64_t calc_mean(uint64_t* data, size_t size); -uint64_t calc_median(uint64_t* data, size_t size); -uint64_t calc_var(uint64_t* data, size_t size, uint64_t mean); -void print_desc_stats(const char* name, uint64_t* data, size_t size); \ No newline at end of file +uint64_t calc_mean(const uint64_t* data, size_t size); +uint64_t calc_median(const uint64_t* data, size_t size); +uint64_t calc_var(const uint64_t* data, size_t size, uint64_t mean); +void print_desc_stats(const char* name, const char* indentation, const uint64_t* data, size_t size); \ No newline at end of file -- GitLab From 449b7a8ec73a8c915a17f58363add40c20899a7f Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Mon, 2 Aug 2021 18:15:08 +0200 Subject: [PATCH 5/5] add dataref target --- Makefile | 18 +++++++++++------- bench.c | 3 +-- stats.c | 2 +- tools/yaml2dataref.sh | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+), 10 deletions(-) create mode 100755 tools/yaml2dataref.sh diff --git a/Makefile b/Makefile index 5cd7361..6942f4a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,6 @@ BENCH_MAIN := bench.c SYSCALLS := blocking io-uring io-uring-sqpoll io-uring-no-syscall epoll paio-sig paio-thrd -STATS ?= - OBJ := $(addprefix bench-,$(SYSCALLS)) LDFLAGS := -luring -pthread -lrt -lm @@ -10,20 +8,26 @@ LDFLAGS := -luring -pthread -lrt -lm CFLAGS := -Werror -Wall -g -O3 # CFLAGS := -Werror -Wall -g -O0 -.PHONY: all clean eval eval-stats docker-eval docker-eval-stats check +.PHONY: all clean eval stats dataref docker-eval docker-stats docker-dataref check eval: all - @for syscall in $(SYSCALLS); do ./bench-$$syscall $(STATS); done + @for syscall in $(SYSCALLS); do echo -n "$$syscall "; ./bench-$$syscall; done + +stats: + @for syscall in $(SYSCALLS); do echo "$$syscall:"; ./bench-$$syscall --stats; done -eval-stats: - @$(MAKE) STATS=--stats eval +dataref: + @$(MAKE) eval-stats | tools/yaml2dataref.sh docker-eval: ./docker.sh make eval -docker-eval-stats: +docker-stats: ./docker.sh make eval-stats +docker-dataref: + ./docker.sh make dataref + all: $(OBJ) define generateTargets diff --git a/bench.c b/bench.c index a1d9ef8..353ddd6 100644 --- a/bench.c +++ b/bench.c @@ -54,13 +54,12 @@ int main(int argc, char *argv[]) { } if (print_stats) { - printf("%s:\n", argv[0]); print_desc_stats("nanos", " ", nanos, exp_iterations); print_desc_stats("cycles", " ", cycles, exp_iterations); } else { uint64_t avg_nanos = calc_mean(nanos, exp_iterations); uint64_t avg_cycles = calc_mean(cycles, exp_iterations); - printf("%s avg-nanos: %lu, avg-cycles: %lu\n", argv[0], avg_nanos, avg_cycles); + printf("avg-nanos: %lu, avg-cycles: %lu\n", avg_nanos, avg_cycles); } free(nanos); diff --git a/stats.c b/stats.c index b7a621d..70a581f 100644 --- a/stats.c +++ b/stats.c @@ -62,7 +62,7 @@ void print_desc_stats(const char* name, const char* indentation, const uint64_t* const uint64_t var = calc_var(data, size, mean); const uint64_t std = (uint64_t)sqrt((double)var); - printf("%s%s\n", indentation, name); + printf("%s%s:\n", indentation, name); printf("%s%smin: %lu\n", indentation, indentation, min); printf("%s%smax: %lu\n", indentation, indentation, max); printf("%s%smedian: %lu\n", indentation, indentation, median); diff --git a/tools/yaml2dataref.sh b/tools/yaml2dataref.sh new file mode 100755 index 0000000..44de499 --- /dev/null +++ b/tools/yaml2dataref.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# taken from here: +# https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script +s='[[:space:]]*' +w='[a-zA-Z0-9_]*' +fs=$(echo @|tr @ '\034') +sed -ne "s|^\($s\):|\1|" \ + -e "s|^\($s\)\($w\)$s:${s}[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \ + -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" | +awk -F"$fs" '{ + indent = length($1)/2; + vname[indent] = $2; + for (i in vname) {if (i > indent) {delete vname[i]}} + if (length($3) > 0) { + vn=""; for (i=0; i