diff mbox series

[iproute2/net-next,v3] tc: B.W limits can now be specified in %.

Message ID 20171117204335.GA17658@gmail.com
State Changes Requested, archived
Delegated to: stephen hemminger
Headers show
Series [iproute2/net-next,v3] tc: B.W limits can now be specified in %. | expand

Commit Message

Nishanth Devarajan Nov. 17, 2017, 8:43 p.m. UTC
This patch adapts the tc command line interface to allow bandwidth limits
to be specified as a percentage of the interface's capacity.

Adding this functionality requires passing the specified device string to
each class/qdisc which changes the prototype for a couple of functions: the
.parse_qopt and .parse_copt interfaces. The device string is a required
parameter for tc-qdisc and tc-class, and when not specified, the kernel
returns ENODEV. In this patch, if the user tries to specify a bandwidth
percentage without naming the device, we return an error from userspace.

v2:
* Modified and moved int read_prop() from ip/iptuntap.c to lib/utils.c,
to make it accessible to tc. 

v3:
* Modified and moved int parse_percent() from tc/q_netem.c to ib/util.c for
use in tc.

* Changed couple variable names in int parse_percent_rate().

* Handled showing error message when device speed is unknown.

* Updated man page to warn users that when specifying rates in %, tc only
uses the current device speed and does not recalculate if it changes after.

During cases when properties (like device speed) are unknown, read_prop()
assumes that if the property file can be opened but not read, it means
that the property is unknown.

Signed-off by: Nishanth Devarajan<ndev2021@gmail.com>

---
 include/utils.h |  2 ++
 ip/iptuntap.c   | 32 ---------------------------
 lib/utils.c     | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 man/man8/tc.8   |  5 ++++-
 tc/q_atm.c      |  2 +-
 tc/q_cbq.c      | 25 ++++++++++++++++-----
 tc/q_choke.c    |  9 ++++++--
 tc/q_clsact.c   |  2 +-
 tc/q_codel.c    |  2 +-
 tc/q_drr.c      |  4 ++--
 tc/q_dsmark.c   |  4 ++--
 tc/q_fifo.c     |  2 +-
 tc/q_fq.c       | 16 +++++++++++---
 tc/q_fq_codel.c |  2 +-
 tc/q_gred.c     |  9 ++++++--
 tc/q_hfsc.c     | 45 +++++++++++++++++++++++++-------------
 tc/q_hhf.c      |  2 +-
 tc/q_htb.c      | 18 +++++++++++----
 tc/q_ingress.c  |  2 +-
 tc/q_mqprio.c   |  2 +-
 tc/q_multiq.c   |  2 +-
 tc/q_netem.c    | 23 ++++++-------------
 tc/q_pie.c      |  2 +-
 tc/q_prio.c     |  2 +-
 tc/q_qfq.c      |  4 ++--
 tc/q_red.c      |  9 ++++++--
 tc/q_rr.c       |  2 +-
 tc/q_sfb.c      |  2 +-
 tc/q_sfq.c      |  2 +-
 tc/q_tbf.c      | 16 +++++++++++---
 tc/tc.c         |  2 +-
 tc/tc_class.c   |  2 +-
 tc/tc_qdisc.c   |  2 +-
 tc/tc_util.c    | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 tc/tc_util.h    |  7 ++++--
 35 files changed, 283 insertions(+), 110 deletions(-)

Comments

Stephen Hemminger Nov. 17, 2017, 8:54 p.m. UTC | #1
On Sat, 18 Nov 2017 02:13:38 +0530
Nishanth Devarajan <ndev2021@gmail.com> wrote:

> +	result = strtoul(buf, &endp, 0);
> +
> +	if (*endp || buf == endp) {
> +		fprintf(stderr, "value \"%s\" in file %s is not a number\n",
> +			buf, fname);
> +		goto out;
> +	}
> +
> +	if (result == ULONG_MAX && errno == ERANGE) {
> +		fprintf(stderr, "strtoul %s: %s", fname, strerror(errno));
> +		goto out;
> +	}

Since speed value of unknown is represented as "-1" I think you need to
change this API to take signed value (ie use strtol)
Stephen Hemminger Nov. 17, 2017, 9:12 p.m. UTC | #2
On Sat, 18 Nov 2017 02:13:38 +0530
Nishanth Devarajan <ndev2021@gmail.com> wrote:

> diff --git a/tc/tc_util.h b/tc/tc_util.h
> index 583a21a..7b7420a 100644
> --- a/tc/tc_util.h
> +++ b/tc/tc_util.h
> @@ -24,14 +24,14 @@ struct qdisc_util {
>  	struct  qdisc_util *next;
>  	const char *id;
>  	int (*parse_qopt)(struct qdisc_util *qu, int argc,
> -			  char **argv, struct nlmsghdr *n);
> +			  char **argv, struct nlmsghdr *n, char *dev);

One more nit...
Since parsing queue options should not modify the device name, that should
be const char *.
Stephen Hemminger Nov. 24, 2017, 7:25 p.m. UTC | #3
On Sat, 18 Nov 2017 02:13:38 +0530
Nishanth Devarajan <ndev2021@gmail.com> wrote:

> This patch adapts the tc command line interface to allow bandwidth limits
> to be specified as a percentage of the interface's capacity.
> 
> Adding this functionality requires passing the specified device string to
> each class/qdisc which changes the prototype for a couple of functions: the
> .parse_qopt and .parse_copt interfaces. The device string is a required
> parameter for tc-qdisc and tc-class, and when not specified, the kernel
> returns ENODEV. In this patch, if the user tries to specify a bandwidth
> percentage without naming the device, we return an error from userspace.
> 
> v2:
> * Modified and moved int read_prop() from ip/iptuntap.c to lib/utils.c,
> to make it accessible to tc. 
> 
> v3:
> * Modified and moved int parse_percent() from tc/q_netem.c to ib/util.c for
> use in tc.
> 
> * Changed couple variable names in int parse_percent_rate().
> 
> * Handled showing error message when device speed is unknown.
> 
> * Updated man page to warn users that when specifying rates in %, tc only
> uses the current device speed and does not recalculate if it changes after.
> 
> During cases when properties (like device speed) are unknown, read_prop()
> assumes that if the property file can be opened but not read, it means
> that the property is unknown.
> 
> Signed-off by: Nishanth Devarajan<ndev2021@gmail.com>
> 

Applied, but there were three things that I needed to change:
  1. The DCO tag is "Signed-off-by" not "Signed-off by"
  2. The revision history should be below the cut line --- in the mail message
     so that it doesn't end up in the commit message.
  3. The qopt function declarations now are a really long line.
     I will break them up.
Nishanth Devarajan Nov. 24, 2017, 8:43 p.m. UTC | #4
On Fri, Nov 24, 2017 at 11:25:28AM -0800, Stephen Hemminger wrote:
> On Sat, 18 Nov 2017 02:13:38 +0530
> Nishanth Devarajan <ndev2021@gmail.com> wrote:
> 
> > This patch adapts the tc command line interface to allow bandwidth limits
> > to be specified as a percentage of the interface's capacity.
> > 
> > Adding this functionality requires passing the specified device string to
> > each class/qdisc which changes the prototype for a couple of functions: the
> > .parse_qopt and .parse_copt interfaces. The device string is a required
> > parameter for tc-qdisc and tc-class, and when not specified, the kernel
> > returns ENODEV. In this patch, if the user tries to specify a bandwidth
> > percentage without naming the device, we return an error from userspace.
> > 
> > v2:
> > * Modified and moved int read_prop() from ip/iptuntap.c to lib/utils.c,
> > to make it accessible to tc. 
> > 
> > v3:
> > * Modified and moved int parse_percent() from tc/q_netem.c to ib/util.c for
> > use in tc.
> > 
> > * Changed couple variable names in int parse_percent_rate().
> > 
> > * Handled showing error message when device speed is unknown.
> > 
> > * Updated man page to warn users that when specifying rates in %, tc only
> > uses the current device speed and does not recalculate if it changes after.
> > 
> > During cases when properties (like device speed) are unknown, read_prop()
> > assumes that if the property file can be opened but not read, it means
> > that the property is unknown.
> > 
> > Signed-off by: Nishanth Devarajan<ndev2021@gmail.com>
> > 
> 
> Applied, but there were three things that I needed to change:
>   1. The DCO tag is "Signed-off-by" not "Signed-off by"
>   2. The revision history should be below the cut line --- in the mail message
>      so that it doesn't end up in the commit message.
>   3. The qopt function declarations now are a really long line.
>      I will break them up.
>

Thanks for the help, and will do, I'll keep the feedback in mind for
future patches, thanks.

-Nishanth
diff mbox series

Patch

diff --git a/include/utils.h b/include/utils.h
index 3d91c50..9377266 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -87,6 +87,8 @@  int get_prefix(inet_prefix *dst, char *arg, int family);
 int mask2bits(__u32 netmask);
 int get_addr_ila(__u64 *val, const char *arg);
 
+int read_prop(const char *dev, char *prop, long *value);
+int parse_percent(double *val, const char *str);
 int get_hex(char c);
 int get_integer(int *val, const char *arg, int base);
 int get_unsigned(unsigned *val, const char *arg, int base);
diff --git a/ip/iptuntap.c b/ip/iptuntap.c
index b46e452..09f2be2 100644
--- a/ip/iptuntap.c
+++ b/ip/iptuntap.c
@@ -223,38 +223,6 @@  static int do_del(int argc, char **argv)
 	return tap_del_ioctl(&ifr);
 }
 
-static int read_prop(char *dev, char *prop, long *value)
-{
-	char fname[IFNAMSIZ+25], buf[80], *endp;
-	ssize_t len;
-	int fd;
-	long result;
-
-	sprintf(fname, "/sys/class/net/%s/%s", dev, prop);
-	fd = open(fname, O_RDONLY);
-	if (fd < 0) {
-		if (strcmp(prop, "tun_flags"))
-			fprintf(stderr, "open %s: %s\n", fname,
-				strerror(errno));
-		return -1;
-	}
-	len = read(fd, buf, sizeof(buf)-1);
-	close(fd);
-	if (len < 0) {
-		fprintf(stderr, "read %s: %s", fname, strerror(errno));
-		return -1;
-	}
-
-	buf[len] = 0;
-	result = strtol(buf, &endp, 0);
-	if (*endp != '\n') {
-		fprintf(stderr, "Failed to parse %s\n", fname);
-		return -1;
-	}
-	*value = result;
-	return 0;
-}
-
 static void print_flags(long flags)
 {
 	if (flags & IFF_TUN)
diff --git a/lib/utils.c b/lib/utils.c
index 4f2fa28..9d5ba2a 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -39,6 +39,74 @@ 
 int resolve_hosts;
 int timestamp_short;
 
+int read_prop(const char *dev, char *prop, long *value)
+{
+	char fname[128], buf[80], *endp, *nl;
+	FILE *fp;
+	long result;
+	int ret;
+
+	ret = snprintf(fname, sizeof(fname), "/sys/class/net/%s/%s",
+			dev, prop);
+
+	if (ret <= 0 || ret >= sizeof(fname)) {
+		fprintf(stderr, "could not build pathname for property\n");
+		return -1;
+	}
+
+	fp = fopen(fname, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "fopen %s: %s\n", fname, strerror(errno));
+		return -1;
+	}
+
+	if (!fgets(buf, sizeof(buf), fp)) {
+		fprintf(stderr, "property \"%s\" in file %s is currently unknown\n", prop, fname);
+		fclose(fp);
+		goto out;
+	}
+
+	nl = strchr(buf, '\n');
+	if (nl)
+		*nl = '\0';
+
+	fclose(fp);
+	result = strtoul(buf, &endp, 0);
+
+	if (*endp || buf == endp) {
+		fprintf(stderr, "value \"%s\" in file %s is not a number\n",
+			buf, fname);
+		goto out;
+	}
+
+	if (result == ULONG_MAX && errno == ERANGE) {
+		fprintf(stderr, "strtoul %s: %s", fname, strerror(errno));
+		goto out;
+	}
+
+	*value = result;
+	return 0;
+out:
+	fprintf(stderr, "Failed to parse %s\n", fname);
+	return -1;
+}
+
+/* Parse a percent e.g: '30%'
+ * return: 0 = ok, -1 = error, 1 = out of range
+ */
+int parse_percent(double *val, const char *str)
+{
+	char *p;
+
+	*val = strtod(str, &p) / 100.;
+	if (*val == HUGE_VALF || *val == HUGE_VALL)
+		return 1;
+	if (*val == 0.0 || (*p && strcmp(p, "%")))
+		return -1;
+
+	return 0;
+}
+
 int get_hex(char c)
 {
 	if (c >= 'A' && c <= 'F')
diff --git a/man/man8/tc.8 b/man/man8/tc.8
index f96911a..263dc75 100644
--- a/man/man8/tc.8
+++ b/man/man8/tc.8
@@ -443,7 +443,10 @@  see the man pages for individual qdiscs.
 RATES
 Bandwidths or rates.
 These parameters accept a floating point number, possibly followed by
-a unit (both SI and IEC units supported).
+either a unit (both SI and IEC units supported), or a float followed by a '%'
+character to specify the rate as a percentage of the device's speed
+(e.g. 5%, 99.5%). Warning: specifying the rate as a percentage means a fraction
+of the current speed; if the speed changes, the value will not be recalculated.
 .RS
 .TP
 bit or a bare number
diff --git a/tc/q_atm.c b/tc/q_atm.c
index 570e7be..7dfd811 100644
--- a/tc/q_atm.c
+++ b/tc/q_atm.c
@@ -44,7 +44,7 @@  static void explain(void)
 
 
 static int atm_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
-   struct nlmsghdr *n)
+	struct nlmsghdr *n, char *dev)
 {
 	struct sockaddr_atmsvc addr = {};
 	struct atm_qos qos;
diff --git a/tc/q_cbq.c b/tc/q_cbq.c
index e00d4e3..284a874 100644
--- a/tc/q_cbq.c
+++ b/tc/q_cbq.c
@@ -46,7 +46,7 @@  static void explain1(char *arg)
 }
 
 
-static int cbq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int cbq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	struct tc_ratespec r = {};
 	struct tc_cbq_lssopt lss = {};
@@ -62,7 +62,12 @@  static int cbq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nl
 		if (matches(*argv, "bandwidth") == 0 ||
 		    matches(*argv, "rate") == 0) {
 			NEXT_ARG();
-			if (get_rate(&r.rate, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&r.rate, *argv, dev)) {
+					explain1("bandwidth");
+					return -1;
+				}
+			} else if (get_rate(&r.rate, *argv)) {
 				explain1("bandwidth");
 				return -1;
 			}
@@ -176,7 +181,7 @@  static int cbq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nl
 	return 0;
 }
 
-static int cbq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int cbq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int wrr_ok = 0, fopt_ok = 0;
 	struct tc_ratespec r = {};
@@ -196,13 +201,23 @@  static int cbq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str
 	while (argc > 0) {
 		if (matches(*argv, "rate") == 0) {
 			NEXT_ARG();
-			if (get_rate(&r.rate, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&r.rate, *argv, dev)) {
+					explain1("rate");
+					return -1;
+				}
+			} else if (get_rate(&r.rate, *argv)) {
 				explain1("rate");
 				return -1;
 			}
 		} else if (matches(*argv, "bandwidth") == 0) {
 			NEXT_ARG();
-			if (get_rate(&bndw, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&bndw, *argv, dev)) {
+					explain1("bandwidth");
+					return -1;
+				}
+			} else if (get_rate(&bndw, *argv)) {
 				explain1("bandwidth");
 				return -1;
 			}
diff --git a/tc/q_choke.c b/tc/q_choke.c
index 726914b..17d70a4 100644
--- a/tc/q_choke.c
+++ b/tc/q_choke.c
@@ -31,7 +31,7 @@  static void explain(void)
 }
 
 static int choke_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			   struct nlmsghdr *n)
+			   struct nlmsghdr *n, char *dev)
 {
 	struct tc_red_qopt opt = {};
 	unsigned int burst = 0;
@@ -53,7 +53,12 @@  static int choke_parse_opt(struct qdisc_util *qu, int argc, char **argv,
 			}
 		} else if (strcmp(*argv, "bandwidth") == 0) {
 			NEXT_ARG();
-			if (get_rate(&rate, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&rate, *argv, dev)) {
+					fprintf(stderr, "Illegal \"bandwidth\"\n");
+					return -1;
+				}
+			} else if (get_rate(&rate, *argv)) {
 				fprintf(stderr, "Illegal \"bandwidth\"\n");
 				return -1;
 			}
diff --git a/tc/q_clsact.c b/tc/q_clsact.c
index e2a1a71..89028e6 100644
--- a/tc/q_clsact.c
+++ b/tc/q_clsact.c
@@ -10,7 +10,7 @@  static void explain(void)
 }
 
 static int clsact_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			    struct nlmsghdr *n)
+			    struct nlmsghdr *n, char *dev)
 {
 	if (argc > 0) {
 		fprintf(stderr, "What is \"%s\"?\n", *argv);
diff --git a/tc/q_codel.c b/tc/q_codel.c
index 253629e..170cd0a 100644
--- a/tc/q_codel.c
+++ b/tc/q_codel.c
@@ -58,7 +58,7 @@  static void explain(void)
 }
 
 static int codel_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			   struct nlmsghdr *n)
+			   struct nlmsghdr *n, char *dev)
 {
 	unsigned int limit = 0;
 	unsigned int target = 0;
diff --git a/tc/q_drr.c b/tc/q_drr.c
index 50623c2..3085268 100644
--- a/tc/q_drr.c
+++ b/tc/q_drr.c
@@ -33,7 +33,7 @@  static void explain2(void)
 }
 
 
-static int drr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int drr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	while (argc) {
 		if (strcmp(*argv, "help") == 0) {
@@ -49,7 +49,7 @@  static int drr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nl
 }
 
 static int drr_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
-			       struct nlmsghdr *n)
+			       struct nlmsghdr *n, char *dev)
 {
 	struct rtattr *tail;
 	__u32 tmp;
diff --git a/tc/q_dsmark.c b/tc/q_dsmark.c
index 0aab387..13d2d31 100644
--- a/tc/q_dsmark.c
+++ b/tc/q_dsmark.c
@@ -25,7 +25,7 @@  static void explain(void)
 
 
 static int dsmark_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-    struct nlmsghdr *n)
+	struct nlmsghdr *n, char *dev)
 {
 	struct rtattr *tail;
 	__u16 ind;
@@ -84,7 +84,7 @@  static void explain_class(void)
 
 
 static int dsmark_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
-   struct nlmsghdr *n)
+	struct nlmsghdr *n, char *dev)
 {
 	struct rtattr *tail;
 	__u8 tmp;
diff --git a/tc/q_fifo.c b/tc/q_fifo.c
index c3e9088..f0ff1ab 100644
--- a/tc/q_fifo.c
+++ b/tc/q_fifo.c
@@ -27,7 +27,7 @@  static void explain(void)
 	fprintf(stderr, "Usage: ... <[p|b]fifo | pfifo_head_drop> [ limit NUMBER ]\n");
 }
 
-static int fifo_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int fifo_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int ok = 0;
 	struct tc_fifo_qopt opt = {};
diff --git a/tc/q_fq.c b/tc/q_fq.c
index 49ebeef..9b0bb93 100644
--- a/tc/q_fq.c
+++ b/tc/q_fq.c
@@ -71,7 +71,7 @@  static unsigned int ilog2(unsigned int val)
 }
 
 static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			struct nlmsghdr *n)
+			struct nlmsghdr *n, char *dev)
 {
 	unsigned int plimit;
 	unsigned int flow_plimit;
@@ -118,7 +118,12 @@  static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
 			}
 		} else if (strcmp(*argv, "maxrate") == 0) {
 			NEXT_ARG();
-			if (get_rate(&maxrate, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&maxrate, *argv, dev)) {
+					fprintf(stderr, "Illegal \"maxrate\"\n");
+					return -1;
+				}
+			} else if (get_rate(&maxrate, *argv)) {
 				fprintf(stderr, "Illegal \"maxrate\"\n");
 				return -1;
 			}
@@ -132,7 +137,12 @@  static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
 			set_low_rate_threshold = true;
 		} else if (strcmp(*argv, "defrate") == 0) {
 			NEXT_ARG();
-			if (get_rate(&defrate, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&defrate, *argv, dev)) {
+					fprintf(stderr, "Illegal \"defrate\"\n");
+					return -1;
+				}
+			} else if (get_rate(&defrate, *argv)) {
 				fprintf(stderr, "Illegal \"defrate\"\n");
 				return -1;
 			}
diff --git a/tc/q_fq_codel.c b/tc/q_fq_codel.c
index 1eac140..ef700cd 100644
--- a/tc/q_fq_codel.c
+++ b/tc/q_fq_codel.c
@@ -56,7 +56,7 @@  static void explain(void)
 }
 
 static int fq_codel_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			      struct nlmsghdr *n)
+			      struct nlmsghdr *n, char *dev)
 {
 	unsigned int limit = 0;
 	unsigned int flows = 0;
diff --git a/tc/q_gred.c b/tc/q_gred.c
index 2eb906d..18d96c9 100644
--- a/tc/q_gred.c
+++ b/tc/q_gred.c
@@ -116,7 +116,7 @@  static int init_gred(struct qdisc_util *qu, int argc, char **argv,
 /*
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 */
-static int gred_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int gred_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int ok = 0;
 	struct tc_gred_qopt opt = { 0 };
@@ -199,7 +199,12 @@  static int gred_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct n
 			ok++;
 		} else if (strcmp(*argv, "bandwidth") == 0) {
 			NEXT_ARG();
-			if (get_rate(&rate, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&rate, *argv, dev)) {
+					fprintf(stderr, "Illegal \"bandwidth\"\n");
+					return -1;
+				}
+			} else if (get_rate(&rate, *argv)) {
 				fprintf(stderr, "Illegal \"bandwidth\"\n");
 				return -1;
 			}
diff --git a/tc/q_hfsc.c b/tc/q_hfsc.c
index dc9fed9..b0923ab 100644
--- a/tc/q_hfsc.c
+++ b/tc/q_hfsc.c
@@ -23,7 +23,7 @@ 
 #include "utils.h"
 #include "tc_util.h"
 
-static int hfsc_get_sc(int *, char ***, struct tc_service_curve *);
+static int hfsc_get_sc(int *, char ***, struct tc_service_curve *, char *);
 
 
 static void
@@ -70,7 +70,7 @@  explain1(char *arg)
 }
 
 static int
-hfsc_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+hfsc_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	struct tc_hfsc_qopt qopt = {};
 
@@ -141,7 +141,7 @@  hfsc_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
 
 static int
 hfsc_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
-		     struct nlmsghdr *n)
+		     struct nlmsghdr *n, char *dev)
 {
 	struct tc_service_curve rsc = {}, fsc = {}, usc = {};
 	int rsc_ok = 0, fsc_ok = 0, usc_ok = 0;
@@ -150,21 +150,21 @@  hfsc_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
 	while (argc > 0) {
 		if (matches(*argv, "rt") == 0) {
 			NEXT_ARG();
-			if (hfsc_get_sc(&argc, &argv, &rsc) < 0) {
+			if (hfsc_get_sc(&argc, &argv, &rsc, dev) < 0) {
 				explain1("rt");
 				return -1;
 			}
 			rsc_ok = 1;
 		} else if (matches(*argv, "ls") == 0) {
 			NEXT_ARG();
-			if (hfsc_get_sc(&argc, &argv, &fsc) < 0) {
+			if (hfsc_get_sc(&argc, &argv, &fsc, dev) < 0) {
 				explain1("ls");
 				return -1;
 			}
 			fsc_ok = 1;
 		} else if (matches(*argv, "sc") == 0) {
 			NEXT_ARG();
-			if (hfsc_get_sc(&argc, &argv, &rsc) < 0) {
+			if (hfsc_get_sc(&argc, &argv, &rsc, dev) < 0) {
 				explain1("sc");
 				return -1;
 			}
@@ -173,7 +173,7 @@  hfsc_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
 			fsc_ok = 1;
 		} else if (matches(*argv, "ul") == 0) {
 			NEXT_ARG();
-			if (hfsc_get_sc(&argc, &argv, &usc) < 0) {
+			if (hfsc_get_sc(&argc, &argv, &usc, dev) < 0) {
 				explain1("ul");
 				return -1;
 			}
@@ -281,7 +281,7 @@  struct qdisc_util hfsc_qdisc_util = {
 };
 
 static int
-hfsc_get_sc1(int *argcp, char ***argvp, struct tc_service_curve *sc)
+hfsc_get_sc1(int *argcp, char ***argvp, struct tc_service_curve *sc, char *dev)
 {
 	char **argv = *argvp;
 	int argc = *argcp;
@@ -289,7 +289,12 @@  hfsc_get_sc1(int *argcp, char ***argvp, struct tc_service_curve *sc)
 
 	if (matches(*argv, "m1") == 0) {
 		NEXT_ARG();
-		if (get_rate(&m1, *argv) < 0) {
+		if (strchr(*argv, '%')) {
+			if (get_percent_rate(&m1, *argv, dev)) {
+				explain1("m1");
+				return -1;
+			}
+		} else if (get_rate(&m1, *argv) < 0) {
 			explain1("m1");
 			return -1;
 		}
@@ -307,7 +312,12 @@  hfsc_get_sc1(int *argcp, char ***argvp, struct tc_service_curve *sc)
 
 	if (matches(*argv, "m2") == 0) {
 		NEXT_ARG();
-		if (get_rate(&m2, *argv) < 0) {
+		if (strchr(*argv, '%')) {
+			if (get_percent_rate(&m2, *argv, dev)) {
+				explain1("m2");
+				return -1;
+			}
+		} else if (get_rate(&m2, *argv) < 0) {
 			explain1("m2");
 			return -1;
 		}
@@ -324,7 +334,7 @@  hfsc_get_sc1(int *argcp, char ***argvp, struct tc_service_curve *sc)
 }
 
 static int
-hfsc_get_sc2(int *argcp, char ***argvp, struct tc_service_curve *sc)
+hfsc_get_sc2(int *argcp, char ***argvp, struct tc_service_curve *sc, char *dev)
 {
 	char **argv = *argvp;
 	int argc = *argcp;
@@ -350,7 +360,12 @@  hfsc_get_sc2(int *argcp, char ***argvp, struct tc_service_curve *sc)
 
 	if (matches(*argv, "rate") == 0) {
 		NEXT_ARG();
-		if (get_rate(&rate, *argv) < 0) {
+		if (strchr(*argv, '%')) {
+			if (get_percent_rate(&rate, *argv, dev)) {
+				explain1("rate");
+				return -1;
+			}
+		} else if (get_rate(&rate, *argv) < 0) {
 			explain1("rate");
 			return -1;
 		}
@@ -386,10 +401,10 @@  hfsc_get_sc2(int *argcp, char ***argvp, struct tc_service_curve *sc)
 }
 
 static int
-hfsc_get_sc(int *argcp, char ***argvp, struct tc_service_curve *sc)
+hfsc_get_sc(int *argcp, char ***argvp, struct tc_service_curve *sc, char *dev)
 {
-	if (hfsc_get_sc1(argcp, argvp, sc) < 0 &&
-	    hfsc_get_sc2(argcp, argvp, sc) < 0)
+	if (hfsc_get_sc1(argcp, argvp, sc, dev) < 0 &&
+	    hfsc_get_sc2(argcp, argvp, sc, dev) < 0)
 		return -1;
 
 	if (sc->m1 == 0 && sc->m2 == 0) {
diff --git a/tc/q_hhf.c b/tc/q_hhf.c
index d1f15f9..c60d425 100644
--- a/tc/q_hhf.c
+++ b/tc/q_hhf.c
@@ -25,7 +25,7 @@  static void explain(void)
 }
 
 static int hhf_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			 struct nlmsghdr *n)
+			 struct nlmsghdr *n, char *dev)
 {
 	unsigned int limit = 0;
 	unsigned int quantum = 0;
diff --git a/tc/q_htb.c b/tc/q_htb.c
index db82852..fb52e72 100644
--- a/tc/q_htb.c
+++ b/tc/q_htb.c
@@ -59,7 +59,7 @@  static void explain1(char *arg)
 }
 
 
-static int htb_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int htb_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	unsigned int direct_qlen = ~0U;
 	struct tc_htb_glob opt = {
@@ -108,7 +108,7 @@  static int htb_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nl
 	return 0;
 }
 
-static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int ok = 0;
 	struct tc_htb_opt opt = {};
@@ -178,7 +178,12 @@  static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str
 				fprintf(stderr, "Double \"ceil\" spec\n");
 				return -1;
 			}
-			if (get_rate64(&ceil64, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate64(&ceil64, *argv, dev)) {
+					explain1("ceil");
+					return -1;
+				}
+			} else if (get_rate64(&ceil64, *argv)) {
 				explain1("ceil");
 				return -1;
 			}
@@ -189,7 +194,12 @@  static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str
 				fprintf(stderr, "Double \"rate\" spec\n");
 				return -1;
 			}
-			if (get_rate64(&rate64, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate64(&rate64, *argv, dev)) {
+					explain1("rate");
+					return -1;
+				}
+			} else if (get_rate64(&rate64, *argv)) {
 				explain1("rate");
 				return -1;
 			}
diff --git a/tc/q_ingress.c b/tc/q_ingress.c
index 31699a8..0ffd82c 100644
--- a/tc/q_ingress.c
+++ b/tc/q_ingress.c
@@ -21,7 +21,7 @@  static void explain(void)
 }
 
 static int ingress_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			     struct nlmsghdr *n)
+			     struct nlmsghdr *n, char *dev)
 {
 	while (argc > 0) {
 		if (strcmp(*argv, "handle") == 0) {
diff --git a/tc/q_mqprio.c b/tc/q_mqprio.c
index 9979852..b568eea 100644
--- a/tc/q_mqprio.c
+++ b/tc/q_mqprio.c
@@ -33,7 +33,7 @@  static void explain(void)
 }
 
 static int mqprio_parse_opt(struct qdisc_util *qu, int argc,
-			    char **argv, struct nlmsghdr *n)
+			    char **argv, struct nlmsghdr *n, char *dev)
 {
 	int idx;
 	struct tc_mqprio_qopt opt = {
diff --git a/tc/q_multiq.c b/tc/q_multiq.c
index ce91fe8..f91ad3a 100644
--- a/tc/q_multiq.c
+++ b/tc/q_multiq.c
@@ -40,7 +40,7 @@  static void explain(void)
 }
 
 static int multiq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			    struct nlmsghdr *n)
+			    struct nlmsghdr *n, char *dev)
 {
 	struct tc_multiq_qopt opt = {};
 
diff --git a/tc/q_netem.c b/tc/q_netem.c
index 82eb46f..3331093 100644
--- a/tc/q_netem.c
+++ b/tc/q_netem.c
@@ -59,20 +59,6 @@  static void set_percent(__u32 *percent, double per)
 	*percent = rint(per * UINT32_MAX);
 }
 
-/* Parse either a fraction '.3' or percent '30%
- * return: 0 = ok, -1 = error, 1 = out of range
- */
-static int parse_percent(double *val, const char *str)
-{
-	char *p;
-
-	*val = strtod(str, &p) / 100.;
-	if (*p && strcmp(p, "%"))
-		return -1;
-
-	return 0;
-}
-
 static int get_percent(__u32 *percent, const char *str)
 {
 	double per;
@@ -167,7 +153,7 @@  static int get_ticks(__u32 *ticks, const char *str)
 }
 
 static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			   struct nlmsghdr *n)
+			   struct nlmsghdr *n, char *dev)
 {
 	int dist_size = 0;
 	struct rtattr *tail;
@@ -396,7 +382,12 @@  static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
 		} else if (matches(*argv, "rate") == 0) {
 			++present[TCA_NETEM_RATE];
 			NEXT_ARG();
-			if (get_rate64(&rate64, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate64(&rate64, *argv, dev)) {
+					explain1("rate");
+					return -1;
+				}
+			} else if (get_rate64(&rate64, *argv)) {
 				explain1("rate");
 				return -1;
 			}
diff --git a/tc/q_pie.c b/tc/q_pie.c
index db72add..b331bb9 100644
--- a/tc/q_pie.c
+++ b/tc/q_pie.c
@@ -39,7 +39,7 @@  static void explain(void)
 #define BETA_MAX 32
 
 static int pie_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			 struct nlmsghdr *n)
+			 struct nlmsghdr *n, char *dev)
 {
 	unsigned int limit   = 0;
 	unsigned int target  = 0;
diff --git a/tc/q_prio.c b/tc/q_prio.c
index 677e25a..bdfc414 100644
--- a/tc/q_prio.c
+++ b/tc/q_prio.c
@@ -27,7 +27,7 @@  static void explain(void)
 	fprintf(stderr, "Usage: ... prio bands NUMBER priomap P1 P2...[multiqueue]\n");
 }
 
-static int prio_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int prio_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int pmap_mode = 0;
 	int idx = 0;
diff --git a/tc/q_qfq.c b/tc/q_qfq.c
index fa270c8..91f683d 100644
--- a/tc/q_qfq.c
+++ b/tc/q_qfq.c
@@ -35,7 +35,7 @@  static void explain_class(void)
 }
 
 static int qfq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			 struct nlmsghdr *n)
+			 struct nlmsghdr *n, char *dev)
 {
 	if (argc > 0) {
 		if (matches(*argv, "help") != 0)
@@ -48,7 +48,7 @@  static int qfq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
 }
 
 static int qfq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
-			       struct nlmsghdr *n)
+			       struct nlmsghdr *n, char *dev)
 {
 	struct rtattr *tail;
 	__u32 tmp;
diff --git a/tc/q_red.c b/tc/q_red.c
index 1564d6e..ddd78b0 100644
--- a/tc/q_red.c
+++ b/tc/q_red.c
@@ -32,7 +32,7 @@  static void explain(void)
 	fprintf(stderr, "               [ecn] [harddrop]\n");
 }
 
-static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	struct tc_red_qopt opt = {};
 	unsigned int burst = 0;
@@ -83,7 +83,12 @@  static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nl
 			}
 		} else if (strcmp(*argv, "bandwidth") == 0) {
 			NEXT_ARG();
-			if (get_rate(&rate, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate(&rate, *argv, dev)) {
+					fprintf(stderr, "Illegal \"bandwidth\"\n");
+					return -1;
+				}
+			} else if (get_rate(&rate, *argv)) {
 				fprintf(stderr, "Illegal \"bandwidth\"\n");
 				return -1;
 			}
diff --git a/tc/q_rr.c b/tc/q_rr.c
index 71ce3ce..341d506 100644
--- a/tc/q_rr.c
+++ b/tc/q_rr.c
@@ -28,7 +28,7 @@  static void explain(void)
 }
 
 
-static int rr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int rr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int pmap_mode = 0;
 	int idx = 0;
diff --git a/tc/q_sfb.c b/tc/q_sfb.c
index d074e87..4f9fa5a 100644
--- a/tc/q_sfb.c
+++ b/tc/q_sfb.c
@@ -48,7 +48,7 @@  static int get_prob(__u32 *val, const char *arg)
 }
 
 static int sfb_parse_opt(struct qdisc_util *qu, int argc, char **argv,
-			 struct nlmsghdr *n)
+			 struct nlmsghdr *n, char *dev)
 {
 	struct tc_sfb_qopt opt = {
 		.rehash_interval = 600*1000,
diff --git a/tc/q_sfq.c b/tc/q_sfq.c
index a875abd..facf2ba 100644
--- a/tc/q_sfq.c
+++ b/tc/q_sfq.c
@@ -34,7 +34,7 @@  static void explain(void)
 	fprintf(stderr, "               [ ecn ] [ harddrop ]\n");
 }
 
-static int sfq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int sfq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int ok = 0, red = 0;
 	struct tc_sfq_qopt_v1 opt = {};
diff --git a/tc/q_tbf.c b/tc/q_tbf.c
index 4955ee4..bd59e10 100644
--- a/tc/q_tbf.c
+++ b/tc/q_tbf.c
@@ -35,7 +35,7 @@  static void explain1(const char *arg, const char *val)
 }
 
 
-static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	int ok = 0;
 	struct tc_tbf_qopt opt = {};
@@ -125,7 +125,12 @@  static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nl
 				fprintf(stderr, "tbf: duplicate \"rate\" specification\n");
 				return -1;
 			}
-			if (get_rate64(&rate64, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate64(&rate64, *argv, dev)) {
+					explain1("rate", *argv);
+					return -1;
+				}
+			} else if (get_rate64(&rate64, *argv)) {
 				explain1("rate", *argv);
 				return -1;
 			}
@@ -136,7 +141,12 @@  static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nl
 				fprintf(stderr, "tbf: duplicate \"peakrate\" specification\n");
 				return -1;
 			}
-			if (get_rate64(&prate64, *argv)) {
+			if (strchr(*argv, '%')) {
+				if (get_percent_rate64(&prate64, *argv, dev)) {
+					explain1("peakrate", *argv);
+					return -1;
+				}
+			} else if (get_rate64(&prate64, *argv)) {
 				explain1("peakrate", *argv);
 				return -1;
 			}
diff --git a/tc/tc.c b/tc/tc.c
index fa71250..0943113 100644
--- a/tc/tc.c
+++ b/tc/tc.c
@@ -59,7 +59,7 @@  static int print_noqopt(struct qdisc_util *qu, FILE *f,
 	return 0;
 }
 
-static int parse_noqopt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+static int parse_noqopt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, char *dev)
 {
 	if (argc) {
 		fprintf(stderr, "Unknown qdisc \"%s\", hence option \"%s\" is unparsable\n", qu->id, *argv);
diff --git a/tc/tc_class.c b/tc/tc_class.c
index c4a6a25..912c65f 100644
--- a/tc/tc_class.c
+++ b/tc/tc_class.c
@@ -128,7 +128,7 @@  static int tc_class_modify(int cmd, unsigned int flags, int argc, char **argv)
 			fprintf(stderr, "Error: Qdisc \"%s\" is classless.\n", k);
 			return 1;
 		}
-		if (q->parse_copt(q, argc, argv, &req.n))
+		if (q->parse_copt(q, argc, argv, &req.n, d))
 			return 1;
 	} else {
 		if (argc) {
diff --git a/tc/tc_qdisc.c b/tc/tc_qdisc.c
index fcb75f2..727d1bc 100644
--- a/tc/tc_qdisc.c
+++ b/tc/tc_qdisc.c
@@ -140,7 +140,7 @@  static int tc_qdisc_modify(int cmd, unsigned int flags, int argc, char **argv)
 
 	if (q) {
 		if (q->parse_qopt) {
-			if (q->parse_qopt(q, argc, argv, &req.n))
+			if (q->parse_qopt(q, argc, argv, &req.n, d))
 				return 1;
 		} else if (argc) {
 			fprintf(stderr, "qdisc '%s' does not support option parsing\n", k);
diff --git a/tc/tc_util.c b/tc/tc_util.c
index 472fc5d..b3184bf 100644
--- a/tc/tc_util.c
+++ b/tc/tc_util.c
@@ -190,6 +190,69 @@  static const struct rate_suffix {
 	{ NULL }
 };
 
+int parse_percent_rate(char *rate, const char *str, const char *dev)
+{
+	long dev_mbit;
+	int ret;
+	double perc, rate_mbit;
+	char *str_perc;
+
+	if (!dev[0]) {
+		fprintf(stderr, "No device specified; specify device to rate limit by percentage\n");
+		return -1;
+	}
+
+	if (read_prop(dev, "speed", &dev_mbit))
+		return -1;
+
+	ret = sscanf(str, "%m[0-9.%]", &str_perc);
+	if (ret != 1)
+		goto malf;
+
+	if (parse_percent(&perc, str_perc))
+		goto malf;
+
+	free(str_perc);
+
+	if (perc > 1.0 || perc < 0.0) {
+		fprintf(stderr, "Invalid rate specified; should be between [0,100]%% but is %s\n", str);
+		return -1;
+	}
+
+	rate_mbit = perc * dev_mbit;
+
+	ret = snprintf(rate, 20, "%lf", rate_mbit);
+	if (ret <= 0 || ret >= 20) {
+		fprintf(stderr, "Unable to parse calculated rate\n");
+		return -1;
+	}
+
+	return 0;
+
+malf:
+	fprintf(stderr, "Specified rate value could not be read or is malformed\n");
+	return -1;
+}
+
+int get_percent_rate(unsigned int *rate, const char *str, char *dev)
+{
+	char r_str[20];
+
+	if (parse_percent_rate(r_str, str, dev))
+		return -1;
+
+	return get_rate(rate, r_str);
+}
+
+int get_percent_rate64(__u64 *rate, const char *str, char *dev)
+{
+	char r_str[20];
+
+	if (parse_percent_rate(r_str, str, dev))
+		return -1;
+
+	return get_rate64(rate, r_str);
+}
 
 int get_rate(unsigned int *rate, const char *str)
 {
diff --git a/tc/tc_util.h b/tc/tc_util.h
index 583a21a..7b7420a 100644
--- a/tc/tc_util.h
+++ b/tc/tc_util.h
@@ -24,14 +24,14 @@  struct qdisc_util {
 	struct  qdisc_util *next;
 	const char *id;
 	int (*parse_qopt)(struct qdisc_util *qu, int argc,
-			  char **argv, struct nlmsghdr *n);
+			  char **argv, struct nlmsghdr *n, char *dev);
 	int (*print_qopt)(struct qdisc_util *qu,
 			  FILE *f, struct rtattr *opt);
 	int (*print_xstats)(struct qdisc_util *qu,
 			    FILE *f, struct rtattr *xstats);
 
 	int (*parse_copt)(struct qdisc_util *qu, int argc,
-			  char **argv, struct nlmsghdr *n);
+			  char **argv, struct nlmsghdr *n, char *dev);
 	int (*print_copt)(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
 };
 
@@ -66,9 +66,12 @@  const char *get_tc_lib(void);
 struct qdisc_util *get_qdisc_kind(const char *str);
 struct filter_util *get_filter_kind(const char *str);
 
+int parse_percent_rate(char *rate, const char *str, const char *dev);
 int get_qdisc_handle(__u32 *h, const char *str);
 int get_rate(unsigned int *rate, const char *str);
+int get_percent_rate(unsigned int *rate, const char *str, char *dev);
 int get_rate64(__u64 *rate, const char *str);
+int get_percent_rate64(__u64 *rate, const char *str, char *dev);
 int get_size(unsigned int *size, const char *str);
 int get_size_and_cell(unsigned int *size, int *cell_log, char *str);
 int get_time(unsigned int *time, const char *str);