From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from [140.186.70.92] (port=57465 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1OjcDO-0006gO-2E for qemu-devel@nongnu.org; Thu, 12 Aug 2010 14:10:33 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.69) (envelope-from ) id 1Ojc1m-0003hE-CP for qemu-devel@nongnu.org; Thu, 12 Aug 2010 13:57:19 -0400 Received: from mail-qw0-f45.google.com ([209.85.216.45]:39248) by eggs.gnu.org with esmtp (Exim 4.69) (envelope-from ) id 1Ojc1m-0003h9-3h for qemu-devel@nongnu.org; Thu, 12 Aug 2010 13:57:18 -0400 Received: by qwh5 with SMTP id 5so1861957qwh.4 for ; Thu, 12 Aug 2010 10:57:17 -0700 (PDT) MIME-Version: 1.0 In-Reply-To: <1281609395-17621-8-git-send-email-stefanha@linux.vnet.ibm.com> References: <1281609395-17621-1-git-send-email-stefanha@linux.vnet.ibm.com> <1281609395-17621-8-git-send-email-stefanha@linux.vnet.ibm.com> From: Blue Swirl Date: Thu, 12 Aug 2010 17:56:57 +0000 Message-ID: Subject: Re: [Qemu-devel] [PATCH 07/14] trace: Add simple built-in tracing backend Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Stefan Hajnoczi Cc: Julien Desfossez , qemu-devel@nongnu.org, Prerna Saxena On Thu, Aug 12, 2010 at 10:36 AM, Stefan Hajnoczi wrote: > This patch adds a simple tracer which produces binary trace files. =C2=A0= To > try out the simple backend: > > $ ./configure --trace-backend=3Dsimple > $ make > > After running QEMU you can pretty-print the trace: > > $ ./simpletrace.py trace-events /tmp/trace.log > > The output of simpletrace.py looks like this: > > =C2=A0qemu_realloc 0.699 ptr=3D0x24363f0 size=3D0x3 newptr=3D0x24363f0 > =C2=A0qemu_free 0.768 ptr=3D0x24363f0 > =C2=A0^ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ^---- timestamp delta (us) > =C2=A0|____ trace event name > > Signed-off-by: Stefan Hajnoczi > > trace: Make trace record fields 64-bit > > Explicitly use 64-bit fields in trace records so that timestamps and > magic numbers work for 32-bit host builds. > > Includes fixes from Prerna Saxena . > > Signed-off-by: Prerna Saxena > Signed-off-by: Stefan Hajnoczi > --- > =C2=A0.gitignore =C2=A0 =C2=A0 | =C2=A0 =C2=A01 + > =C2=A0Makefile =C2=A0 =C2=A0 =C2=A0 | =C2=A0 =C2=A02 + > =C2=A0Makefile.objs =C2=A0| =C2=A0 =C2=A03 + > =C2=A0configure =C2=A0 =C2=A0 =C2=A0| =C2=A0 =C2=A02 +- > =C2=A0simpletrace.c =C2=A0| =C2=A0127 +++++++++++++++++++++++++++++++++++= +++++++++++++++++++++ > =C2=A0simpletrace.h =C2=A0| =C2=A0 26 +++++++++++ > =C2=A0simpletrace.py | =C2=A0 96 ++++++++++++++++++++++++++++++++++++++++= ++ > =C2=A0tracetool =C2=A0 =C2=A0 =C2=A0| =C2=A0 82 +++++++++++++++++++++++++= +++++++++-- > =C2=A08 files changed, 333 insertions(+), 6 deletions(-) > =C2=A0create mode 100644 simpletrace.c > =C2=A0create mode 100644 simpletrace.h > =C2=A0create mode 100755 simpletrace.py > > diff --git a/.gitignore b/.gitignore > index f3f2bb7..72bff98 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -42,6 +42,7 @@ QMP/qmp-commands.txt > =C2=A0*.log > =C2=A0*.pdf > =C2=A0*.pg > +*.pyc > =C2=A0*.toc > =C2=A0*.tp > =C2=A0*.vr > diff --git a/Makefile b/Makefile > index 3c5e6a0..ab91d42 100644 > --- a/Makefile > +++ b/Makefile > @@ -112,6 +112,8 @@ trace.c: $(SRC_PATH)/trace-events config-host.mak > > =C2=A0trace.o: trace.c $(GENERATED_HEADERS) > > +simpletrace.o: simpletrace.c $(GENERATED_HEADERS) > + > =C2=A0###################################################################= ### > > =C2=A0qemu-img.o: qemu-img-cmds.h > diff --git a/Makefile.objs b/Makefile.objs > index c61332d..3ef6d80 100644 > --- a/Makefile.objs > +++ b/Makefile.objs > @@ -269,6 +269,9 @@ libdis-$(CONFIG_SPARC_DIS) +=3D sparc-dis.o > =C2=A0# trace > > =C2=A0trace-obj-y =3D trace.o > +ifeq ($(TRACE_BACKEND),simple) > +trace-obj-y +=3D simpletrace.o > +endif > > =C2=A0vl.o: QEMU_CFLAGS+=3D$(GPROF_CFLAGS) > > diff --git a/configure b/configure > index d3648b2..62dd10d 100755 > --- a/configure > +++ b/configure > @@ -898,7 +898,7 @@ echo " =C2=A0--enable-docs =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0enable documentation build" > =C2=A0echo " =C2=A0--disable-docs =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 disa= ble documentation build" > =C2=A0echo " =C2=A0--disable-vhost-net =C2=A0 =C2=A0 =C2=A0disable vhost-= net acceleration support" > =C2=A0echo " =C2=A0--enable-vhost-net =C2=A0 =C2=A0 =C2=A0 enable vhost-n= et acceleration support" > -echo " =C2=A0--trace-backend=3DB =C2=A0 =C2=A0 =C2=A0 =C2=A0Trace backen= d nop" > +echo " =C2=A0--trace-backend=3DB =C2=A0 =C2=A0 =C2=A0 =C2=A0Trace backen= d nop simple" > =C2=A0echo "" > =C2=A0echo "NOTE: The object files are built at the place where configure= is launched" > =C2=A0exit 1 > diff --git a/simpletrace.c b/simpletrace.c > new file mode 100644 > index 0000000..a6afc51 > --- /dev/null > +++ b/simpletrace.c > @@ -0,0 +1,127 @@ > +/* > + * Simple trace backend > + * > + * Copyright IBM, Corp. 2010 > + * > + * This work is licensed under the terms of the GNU GPL, version 2. =C2= =A0See > + * the COPYING file in the top-level directory. > + * > + */ > + > +#include > +#include > +#include > +#include "trace.h" > + > +/** Trace file header event ID */ > +#define HEADER_EVENT_ID (~(uint64_t)0) /* avoids conflicting with TraceE= ventIDs */ > + > +/** Trace file magic number */ > +#define HEADER_MAGIC 0xf2b177cb0aa429b4ULL > + > +/** Trace file version number, bump if format changes */ > +#define HEADER_VERSION 0 > + > +/** Trace buffer entry */ > +typedef struct { > + =C2=A0 =C2=A0uint64_t event; > + =C2=A0 =C2=A0uint64_t timestamp_ns; > + =C2=A0 =C2=A0uint64_t x1; > + =C2=A0 =C2=A0uint64_t x2; > + =C2=A0 =C2=A0uint64_t x3; > + =C2=A0 =C2=A0uint64_t x4; > + =C2=A0 =C2=A0uint64_t x5; > +} TraceRecord; > + > +enum { > + =C2=A0 =C2=A0TRACE_BUF_LEN =3D 64 * 1024 / sizeof(TraceRecord), > +}; > + > +static TraceRecord trace_buf[TRACE_BUF_LEN]; > +static unsigned int trace_idx; > +static FILE *trace_fp; > + > +static bool write_header(FILE *fp) > +{ > + =C2=A0 =C2=A0TraceRecord header =3D { This should be 'static const'. > + =C2=A0 =C2=A0 =C2=A0 =C2=A0.event =3D HEADER_EVENT_ID, > + =C2=A0 =C2=A0 =C2=A0 =C2=A0.timestamp_ns =3D HEADER_MAGIC, > + =C2=A0 =C2=A0 =C2=A0 =C2=A0.x1 =3D HEADER_VERSION, > + =C2=A0 =C2=A0}; > + > + =C2=A0 =C2=A0return fwrite(&header, sizeof header, 1, fp) =3D=3D 1; > +} > + > +static void flush_trace_buffer(void) > +{ > + =C2=A0 =C2=A0if (!trace_fp) { > + =C2=A0 =C2=A0 =C2=A0 =C2=A0trace_fp =3D fopen("/tmp/trace.log", "w"); > + =C2=A0 =C2=A0 =C2=A0 =C2=A0if (trace_fp) { > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0write_header(trace_fp); > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0atexit(flush_trace_buffer); > + =C2=A0 =C2=A0 =C2=A0 =C2=A0} > + =C2=A0 =C2=A0} > + =C2=A0 =C2=A0if (trace_fp) { > + =C2=A0 =C2=A0 =C2=A0 =C2=A0size_t unused; /* for when fwrite(3) is decl= ared warn_unused_result */ > + =C2=A0 =C2=A0 =C2=A0 =C2=A0unused =3D fwrite(trace_buf, trace_idx * siz= eof(trace_buf[0]), 1, trace_fp); > + =C2=A0 =C2=A0} > + > + =C2=A0 =C2=A0/* Discard written trace records */ > + =C2=A0 =C2=A0trace_idx =3D 0; > +} > + > +static void trace(TraceEventID event, unsigned long x1, > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0unsigned = long x2, unsigned long x3, > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0unsigned = long x4, unsigned long x5) > +{ > + =C2=A0 =C2=A0TraceRecord *rec =3D &trace_buf[trace_idx]; > + =C2=A0 =C2=A0struct timespec ts; > + > + =C2=A0 =C2=A0/* TODO Windows? =C2=A0It would be good to use qemu-timer = here but that isn't > + =C2=A0 =C2=A0 * linked into qemu-tools. =C2=A0Also we should avoid recu= rsion in the tracing > + =C2=A0 =C2=A0 * code, therefore it is useful to be self-contained. > + =C2=A0 =C2=A0 */ > + =C2=A0 =C2=A0clock_gettime(CLOCK_MONOTONIC, &ts); > + > + =C2=A0 =C2=A0rec->event =3D event; > + =C2=A0 =C2=A0rec->timestamp_ns =3D ts.tv_sec * 1000000000LL + ts.tv_nse= c; > + =C2=A0 =C2=A0rec->x1 =3D x1; > + =C2=A0 =C2=A0rec->x2 =3D x2; > + =C2=A0 =C2=A0rec->x3 =3D x3; > + =C2=A0 =C2=A0rec->x4 =3D x4; > + =C2=A0 =C2=A0rec->x5 =3D x5; > + > + =C2=A0 =C2=A0if (++trace_idx =3D=3D TRACE_BUF_LEN) { > + =C2=A0 =C2=A0 =C2=A0 =C2=A0flush_trace_buffer(); > + =C2=A0 =C2=A0} > +} > + > +void trace0(TraceEventID event) > +{ > + =C2=A0 =C2=A0trace(event, 0, 0, 0, 0, 0); > +} > + > +void trace1(TraceEventID event, uint64_t x1) > +{ > + =C2=A0 =C2=A0trace(event, x1, 0, 0, 0, 0); > +} > + > +void trace2(TraceEventID event, uint64_t x1, uint64_t x2) > +{ > + =C2=A0 =C2=A0trace(event, x1, x2, 0, 0, 0); > +} > + > +void trace3(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3) > +{ > + =C2=A0 =C2=A0trace(event, x1, x2, x3, 0, 0); > +} > + > +void trace4(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, u= int64_t x4) > +{ > + =C2=A0 =C2=A0trace(event, x1, x2, x3, x4, 0); > +} > + > +void trace5(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, u= int64_t x4, uint64_t x5) > +{ > + =C2=A0 =C2=A0trace(event, x1, x2, x3, x4, x5); > +} > diff --git a/simpletrace.h b/simpletrace.h > new file mode 100644 > index 0000000..e4e9759 > --- /dev/null > +++ b/simpletrace.h > @@ -0,0 +1,26 @@ > +/* > + * Simple trace backend > + * > + * Copyright IBM, Corp. 2010 > + * > + * This work is licensed under the terms of the GNU GPL, version 2. =C2= =A0See > + * the COPYING file in the top-level directory. > + * > + */ > + > +#ifndef SIMPLETRACE_H > +#define SIMPLETRACE_H > + > +#include Why? > +#include > + > +typedef uint64_t TraceEventID; > + > +void trace0(TraceEventID event); > +void trace1(TraceEventID event, uint64_t x1); > +void trace2(TraceEventID event, uint64_t x1, uint64_t x2); > +void trace3(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3); > +void trace4(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, u= int64_t x4); > +void trace5(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, u= int64_t x4, uint64_t x5); > + > +#endif /* SIMPLETRACE_H */ > diff --git a/simpletrace.py b/simpletrace.py > new file mode 100755 > index 0000000..fdf0eb5 > --- /dev/null > +++ b/simpletrace.py > @@ -0,0 +1,96 @@ > +#!/usr/bin/env python > +# > +# Pretty-printer for simple trace backend binary trace files > +# > +# Copyright IBM, Corp. 2010 > +# > +# This work is licensed under the terms of the GNU GPL, version 2. =C2= =A0See > +# the COPYING file in the top-level directory. > +# > +# For help see docs/tracing.txt > + > +import sys > +import struct > +import re > + > +header_event_id =3D 0xffffffffffffffff > +header_magic =C2=A0 =C2=A0=3D 0xf2b177cb0aa429b4 > +header_version =C2=A0=3D 0 > + > +trace_fmt =3D '=3DQQQQQQQ' > +trace_len =3D struct.calcsize(trace_fmt) > +event_re =C2=A0=3D re.compile(r'(disable\s+)?([a-zA-Z0-9_]+)\(([^)]*)\)\= s+"([^"]*)"') > + > +def err(msg): > + =C2=A0 =C2=A0sys.stderr.write(msg + '\n') > + =C2=A0 =C2=A0sys.exit(1) > + > +def parse_events(fobj): > + =C2=A0 =C2=A0"""Parse a trace-events file.""" > + > + =C2=A0 =C2=A0def get_argnames(args): > + =C2=A0 =C2=A0 =C2=A0 =C2=A0"""Extract argument names from a parameter l= ist.""" > + =C2=A0 =C2=A0 =C2=A0 =C2=A0return tuple(arg.split()[-1].lstrip('*') for= arg in args.split(',')) > + > + =C2=A0 =C2=A0events =3D {} > + =C2=A0 =C2=A0event_num =3D 0 > + =C2=A0 =C2=A0for line in fobj: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0m =3D event_re.match(line.strip()) > + =C2=A0 =C2=A0 =C2=A0 =C2=A0if m is None: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0continue > + > + =C2=A0 =C2=A0 =C2=A0 =C2=A0disable, name, args, fmt =3D m.groups() > + =C2=A0 =C2=A0 =C2=A0 =C2=A0if disable: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0continue > + > + =C2=A0 =C2=A0 =C2=A0 =C2=A0events[event_num] =3D (name,) + get_argnames= (args) > + =C2=A0 =C2=A0 =C2=A0 =C2=A0event_num +=3D 1 > + =C2=A0 =C2=A0return events > + > +def read_record(fobj): > + =C2=A0 =C2=A0"""Deserialize a trace record from a file.""" > + =C2=A0 =C2=A0s =3D fobj.read(trace_len) > + =C2=A0 =C2=A0if len(s) !=3D trace_len: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0return None > + =C2=A0 =C2=A0return struct.unpack(trace_fmt, s) > + > +def read_trace_file(fobj): > + =C2=A0 =C2=A0"""Deserialize trace records from a file.""" > + =C2=A0 =C2=A0header =3D read_record(fobj) > + =C2=A0 =C2=A0if header is None or \ > + =C2=A0 =C2=A0 =C2=A0 header[0] !=3D header_event_id or \ > + =C2=A0 =C2=A0 =C2=A0 header[1] !=3D header_magic or \ > + =C2=A0 =C2=A0 =C2=A0 header[2] !=3D header_version: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0err('not a trace file or incompatible versio= n') > + > + =C2=A0 =C2=A0while True: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0rec =3D read_record(fobj) > + =C2=A0 =C2=A0 =C2=A0 =C2=A0if rec is None: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0break > + > + =C2=A0 =C2=A0 =C2=A0 =C2=A0yield rec > + > +class Formatter(object): > + =C2=A0 =C2=A0def __init__(self, events): > + =C2=A0 =C2=A0 =C2=A0 =C2=A0self.events =3D events > + =C2=A0 =C2=A0 =C2=A0 =C2=A0self.last_timestamp =3D None > + > + =C2=A0 =C2=A0def format_record(self, rec): > + =C2=A0 =C2=A0 =C2=A0 =C2=A0if self.last_timestamp is None: > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.last_timestamp =3D rec[1] > + =C2=A0 =C2=A0 =C2=A0 =C2=A0delta_ns =3D rec[1] - self.last_timestamp > + =C2=A0 =C2=A0 =C2=A0 =C2=A0self.last_timestamp =3D rec[1] > + > + =C2=A0 =C2=A0 =C2=A0 =C2=A0event =3D self.events[rec[0]] > + =C2=A0 =C2=A0 =C2=A0 =C2=A0fields =3D [event[0], '%0.3f' % (delta_ns / = 1000.0)] > + =C2=A0 =C2=A0 =C2=A0 =C2=A0for i in xrange(1, len(event)): > + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0fields.append('%s=3D0x%x' % (e= vent[i], rec[i + 1])) > + =C2=A0 =C2=A0 =C2=A0 =C2=A0return ' '.join(fields) > + > +if len(sys.argv) !=3D 3: > + =C2=A0 =C2=A0err('usage: %s ' % sys.argv[0]) > + > +events =3D parse_events(open(sys.argv[1], 'r')) > +formatter =3D Formatter(events) > +for rec in read_trace_file(open(sys.argv[2], 'rb')): > + =C2=A0 =C2=A0print formatter.format_record(rec) > diff --git a/tracetool b/tracetool > index 86b6446..66df685 100755 > --- a/tracetool > +++ b/tracetool > @@ -13,11 +13,12 @@ set -f > =C2=A0usage() > =C2=A0{ > =C2=A0 =C2=A0 cat >&2 < -usage: $0 --nop [-h | -c] > +usage: $0 [--nop | --simple] [-h | -c] > =C2=A0Generate tracing code for a file on stdin. > > =C2=A0Backends: > - =C2=A0--nop Tracing disabled > + =C2=A0--nop =C2=A0 =C2=A0 Tracing disabled > + =C2=A0--simple =C2=A0Simple built-in backend > > =C2=A0Output formats: > =C2=A0 -h =C2=A0 =C2=A0Generate .h file > @@ -56,16 +57,27 @@ get_argnames() > =C2=A0 =C2=A0 =C2=A0 =C2=A0 name=3D${field%,} > =C2=A0 =C2=A0 =C2=A0 =C2=A0 test "$field" =3D "$name" && continue > > - =C2=A0 =C2=A0 =C2=A0 =C2=A0echo -n "$name, " > + =C2=A0 =C2=A0 =C2=A0 =C2=A0printf "%s" "$name, " > =C2=A0 =C2=A0 done > > =C2=A0 =C2=A0 # Last argument name > =C2=A0 =C2=A0 if [ "$nfields" -gt 1 ] > =C2=A0 =C2=A0 then > - =C2=A0 =C2=A0 =C2=A0 =C2=A0echo -n "$name" > + =C2=A0 =C2=A0 =C2=A0 =C2=A0printf "%s" "$name" > =C2=A0 =C2=A0 fi > =C2=A0} > > +# Get the number of arguments to a trace event > +get_argc() > +{ > + =C2=A0 =C2=A0local name argc > + =C2=A0 =C2=A0argc=3D0 > + =C2=A0 =C2=A0for name in $(get_argnames "$1"); do > + =C2=A0 =C2=A0 =C2=A0 =C2=A0argc=3D$((argc + 1)) > + =C2=A0 =C2=A0done > + =C2=A0 =C2=A0echo $argc > +} > + > =C2=A0# Get the format string for a trace event > =C2=A0get_fmt() > =C2=A0{ > @@ -115,6 +127,66 @@ linetoc_end_nop() > =C2=A0 =C2=A0 return > =C2=A0} > > +linetoh_begin_simple() > +{ > + =C2=A0 =C2=A0cat < +#include "simpletrace.h" > +EOF > + > + =C2=A0 =C2=A0simple_event_num=3D0 > +} > + > +cast_args_to_uint64_t() > +{ > + =C2=A0 =C2=A0local arg > + =C2=A0 =C2=A0for arg in $(get_argnames "$1"); do > + =C2=A0 =C2=A0 =C2=A0 =C2=A0printf "%s" "(uint64_t)(uintptr_t)$arg" > + =C2=A0 =C2=A0done > +} > + > +linetoh_simple() > +{ > + =C2=A0 =C2=A0local name args argc trace_args > + =C2=A0 =C2=A0name=3D$(get_name "$1") > + =C2=A0 =C2=A0args=3D$(get_args "$1") > + =C2=A0 =C2=A0argc=3D$(get_argc "$1") > + > + =C2=A0 =C2=A0trace_args=3D"$simple_event_num" > + =C2=A0 =C2=A0if [ "$argc" -gt 0 ] > + =C2=A0 =C2=A0then > + =C2=A0 =C2=A0 =C2=A0 =C2=A0trace_args=3D"$trace_args, $(cast_args_to_ui= nt64_t "$1")" > + =C2=A0 =C2=A0fi > + > + =C2=A0 =C2=A0cat < +static inline void trace_$name($args) > +{ > + =C2=A0 =C2=A0trace$argc($trace_args); > +} > +EOF > + > + =C2=A0 =C2=A0simple_event_num=3D$((simple_event_num + 1)) > +} > + > +linetoh_end_simple() > +{ > + =C2=A0 =C2=A0return > +} > + > +linetoc_begin_simple() > +{ > + =C2=A0 =C2=A0return > +} > + > +linetoc_simple() > +{ > + =C2=A0 =C2=A0return > +} > + > +linetoc_end_simple() > +{ > + =C2=A0 =C2=A0return > +} > + > =C2=A0# Process stdin by calling begin, line, and end functions for the b= ackend > =C2=A0convert() > =C2=A0{ > @@ -160,7 +232,7 @@ tracetoc() > > =C2=A0# Choose backend > =C2=A0case "$1" in > -"--nop") backend=3D"${1#--}" ;; > +"--nop" | "--simple") backend=3D"${1#--}" ;; > =C2=A0*) usage ;; > =C2=A0esac > =C2=A0shift > -- > 1.7.1 > > >