├── .gitignore ├── AUTHORS ├── LICENSE ├── Makefile ├── README.md ├── bqlmon.c └── bqlmon.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | bqlmon 3 | perf.data* 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Florian Fainelli 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Florian Fainelli . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | This software is provided ``as is'' and any express or implied 17 | warranties, including, but not limited to, the implied warranties of 18 | merchantability and fitness for a particular purpose are 19 | disclaimed. In no event shall author or contributors be liable for any 20 | direct, indirect, incidental, special, exemplary, or consequential 21 | damages (including, but not limited to, procurement of substitute 22 | goods or services; loss of use, data, or profits; or business 23 | interruption) however caused and on any theory of liability, whether 24 | in contract, strict liability, or tort (including negligence or 25 | otherwise) arising in any way out of the use of this software, even if 26 | advised of the possibility of such damage. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APP=bqlmon 2 | all: $(APP) 3 | 4 | CFLAGS?=-Wall -Werror 5 | LIBS=-lncurses 6 | LDFLAGS?= 7 | CC=$(CROSS)gcc 8 | 9 | SRCS:=bqlmon.c 10 | OBJS:=bqlmon.o 11 | HEADERS:=bqlmon.h 12 | 13 | %.o: %.c $(HEADERS) 14 | $(CC) $(CFLAGS) -o $@ -c $< 15 | 16 | $(APP): $(OBJS) 17 | $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(LDFLAGS) 18 | 19 | clean: 20 | -rm -f $(OBJS) $(APP) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Byte Queue Limits monitor 2 | 3 | This program uses the Linux Byte Queue Limits support that some Linux Ethernet 4 | drivers do support. Byte Queue Limits is refered to below as BQL. The program 5 | provides a ncurses interface to monitor the current number of bytes in-flight in 6 | the queue. 7 | 8 | Motivation for this program was twofold: 9 | - make sure that the Broadcom SYSTEMPORT driver did have correct BQL mechanism 10 | as I was writing it 11 | - experiment and do something potentially useful with ncurses 12 | 13 | # Snapshot 14 | 15 | Here is a snapshot of a 32 transmit queue devices: 16 | 17 | ![Sample output](https://raw.githubusercontent.com/ffainelli/bqlmon/gh-pages/bqlmon_snapshot.png); 18 | 19 | # BQL in a nutshell 20 | 21 | BQL basically keeps track of how many bytes are in-flight, that are pending 22 | transmission in a given Ethernet adapter hardware queue. The more information 23 | about the actual hardware queue state we have, the better for software to 24 | accomodate with transmission of network packets. 25 | 26 | Ethernet drivers implementing that feature usually do the following in their 27 | transmit function: 28 | 29 | - prepare the buffer for transmission 30 | - tell the network stack how many bytes are being sent (netdev_tx_sent_queue) 31 | - tell the hardware the buffer should be transmitted 32 | 33 | In their transmit complete function they do the following: 34 | 35 | - count how many packets and bytes have been transmitted 36 | - report how many packets and bytes have been completed 37 | (netdev_tx_completed_queue) 38 | - free the transmitted buffers 39 | 40 | Accurate reporting of how many buffers are in-flight allows for a better sizing 41 | of all software queueing. 42 | 43 | Using BQL also allows highlighting potential transmit queueing and flow control 44 | issues. For instance, if the transmitted buffers are reclaimed in an interrupt 45 | context, but that interrupt never fires, we will quickly queue up many buffers 46 | that are not freed. 47 | 48 | BQL exposes a few sysfs attributes in 49 | /sys/class/net/*interface*/queues/tx-*N*/byte_queue_limits/ which are per-queue 50 | information about: 51 | 52 | - in-flight packets 53 | - limit threshold until the queue is declared congested 54 | - hold time 55 | 56 | -- 57 | Florian Fainelli 58 | -------------------------------------------------------------------------------- /bqlmon.c: -------------------------------------------------------------------------------- 1 | /* 2 | * BQLmon 3 | * 4 | * Copyright (C) 2014, Florian Fainelli 5 | * 6 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 7 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 8 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 9 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 10 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 11 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 12 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 13 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 14 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 15 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 16 | * POSSIBILITY OF SUCH DAMAGE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "bqlmon.h" 38 | 39 | #define DEFAULT_SCALING (1024) // This is a lousy way to do scaling 40 | 41 | /* Locate the network interface and count its number of transmit queues */ 42 | static int bql_sysfs_init(struct bql_ctx *ctx) 43 | { 44 | struct dirent **namelist; 45 | char path[PATH_MAX]; 46 | struct utsname uts; 47 | int maj, min, rel; 48 | int n; 49 | 50 | n = uname(&uts); 51 | if (n < 0) 52 | goto try_file; 53 | 54 | if (strcmp(uts.sysname, "Linux")) { 55 | fprintf(stderr, "Unsupported OS: %s\n", uts.sysname); 56 | return -1; 57 | } 58 | 59 | n = sscanf(uts.release, "%d.%d.%d", &maj, &min, &rel); 60 | if (n < 3) 61 | return -1; 62 | 63 | if (maj < 3 && min < 3) { 64 | fprintf(stderr, "Kernel too old, requires 3.3 for BQL\n"); 65 | return -1; 66 | } 67 | 68 | try_file: 69 | n = snprintf(path, sizeof(path), "/sys/class/net/%s/queues/", 70 | ctx->iface); 71 | if (n <= 0) 72 | return -1; 73 | 74 | n = scandir(path, &namelist, NULL, alphasort); 75 | if (n < 0) { 76 | perror("scandir"); 77 | return n; 78 | } 79 | 80 | while (n--) { 81 | if (!strncmp(namelist[n]->d_name, "tx-", 3)) 82 | ctx->num_queues++; 83 | free(namelist[n]); 84 | } 85 | free(namelist); 86 | 87 | if (ctx->num_queues == 0) { 88 | fprintf(stderr, "Kernel too old, or invalid network device\n"); 89 | return -1; 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | static int bql_get_drv_info(struct bql_ctx *ctx) 96 | { 97 | struct ifreq ifr; 98 | int fd, ret; 99 | 100 | fd = socket(AF_INET, SOCK_DGRAM, 0); 101 | if (fd < 0) 102 | return fd; 103 | 104 | memset(&ifr, 0, sizeof(ifr)); 105 | 106 | strcpy(ifr.ifr_name, ctx->iface); 107 | ctx->info.cmd = ETHTOOL_GDRVINFO; 108 | ifr.ifr_data = (void *)&ctx->info; 109 | 110 | ret = ioctl(fd, SIOCETHTOOL, &ifr); 111 | close(fd); 112 | if (ret < 0) 113 | return ret; 114 | 115 | return 0; 116 | } 117 | 118 | static int bql_sysfs_file_init(struct bql_sysfs_attr *s, const char *dir, 119 | const char *name) 120 | { 121 | char path[PATH_MAX]; 122 | size_t n; 123 | 124 | n = snprintf(path, sizeof(path), "%s/%s", dir, name); 125 | if (n <= 0) 126 | return -1; 127 | 128 | s->fd = open(path, O_RDONLY); 129 | if (s->fd < 0) { 130 | perror("open"); 131 | return s->fd; 132 | } 133 | 134 | return 0; 135 | } 136 | 137 | static int bql_sysfs_file_read(struct bql_sysfs_attr *s) 138 | { 139 | ssize_t n; 140 | off_t r; 141 | 142 | /* Rewind to the beginning */ 143 | r = lseek(s->fd, 0, SEEK_SET); 144 | if (r < 0) { 145 | perror("lseek"); 146 | return r; 147 | } 148 | 149 | n = read(s->fd, s->s_val, sizeof(s->s_val)); 150 | if (n < 0) { 151 | perror("read"); 152 | return n; 153 | } 154 | 155 | n = sscanf(s->s_val, "%d", &s->value); 156 | if (n < 1) 157 | return -1; 158 | 159 | return 0; 160 | } 161 | 162 | static const char *attr_names[] = { 163 | "hold_time", "inflight", "limit", "limit_max", "limit_min" }; 164 | 165 | #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) 166 | 167 | static int bql_queue_init(struct bql_q_ctx *q) 168 | { 169 | char path[PATH_MAX]; 170 | size_t n; 171 | unsigned int i; 172 | int ret; 173 | 174 | n = snprintf(path, sizeof(path), 175 | "/sys/class/net/%s/queues/tx-%d/byte_queue_limits", 176 | q->g->iface, q->queue_num); 177 | if (n <= 0) 178 | return -1; 179 | 180 | for (i = 0; i < ARRAY_SIZE(attr_names); i++) { 181 | ret = bql_sysfs_file_init(&q->attrs[i], path, attr_names[i]); 182 | if (ret) 183 | return ret; 184 | } 185 | 186 | return 0; 187 | } 188 | 189 | static void bql_queue_exit(struct bql_q_ctx *q) 190 | { 191 | unsigned int i; 192 | 193 | for (i = 0; i < ARRAY_SIZE(attr_names); i++) { 194 | if (q->attrs[i].fd >= 0) { 195 | close(q->attrs[i].fd); 196 | q->attrs[i].fd = -1; 197 | } 198 | } 199 | } 200 | 201 | static inline unsigned int bql_poll_one_queue(struct bql_q_ctx *ctx) 202 | { 203 | bql_sysfs_file_read(&ctx->attrs[INFLIGHT]); 204 | bql_sysfs_file_read(&ctx->attrs[LIMIT]); 205 | 206 | return ctx->attrs[INFLIGHT].value; 207 | } 208 | 209 | static void bql_queues_destroy(struct bql_ctx *ctx) 210 | { 211 | unsigned int i; 212 | 213 | for (i = 0; i < ctx->num_queues; i++) 214 | bql_queue_exit(&ctx->queues[i]); 215 | free(ctx->queues); 216 | } 217 | 218 | static int bql_queues_create(struct bql_ctx *ctx) 219 | { 220 | struct bql_q_ctx *tctx; 221 | unsigned int i; 222 | size_t size; 223 | int ret; 224 | 225 | size = sizeof(struct bql_q_ctx) * ctx->num_queues; 226 | 227 | ctx->queues = malloc(size); 228 | if (!ctx->queues) 229 | return -ENOMEM; 230 | 231 | memset(ctx->queues, 0, size); 232 | 233 | for (i = 0; i < ctx->num_queues; i++) { 234 | tctx = &ctx->queues[i]; 235 | tctx->queue_num = i; 236 | tctx->g = ctx; 237 | 238 | ret = bql_queue_init(tctx); 239 | if (ret) { 240 | fprintf(stderr, "failed to initialize queue %d\n", i); 241 | return ret; 242 | } 243 | } 244 | 245 | return ret; 246 | } 247 | 248 | static int get_color_thresh(unsigned int val, unsigned int limit) 249 | { 250 | if (val <= limit / 3) 251 | return 2; 252 | else if (val <= (limit * 2) / 3) 253 | return 3; 254 | else if (val <= limit) 255 | return 1; 256 | else 257 | return 6; 258 | } 259 | 260 | static void bql_draw_arrows(struct bql_ctx *ctx, unsigned int q, 261 | unsigned int limit) 262 | { 263 | unsigned int i; 264 | chtype ch; 265 | int y, x; 266 | 267 | x = q * QUEUE_SPACING + QUEUE_VAL_X - ctx->x_start; 268 | y = ctx->rows - QUEUE_VAL_Y - limit - QUEUE_ARROW_Y; 269 | 270 | if (q == ctx->vq_start && q != 0) { 271 | for (i = 0; i < 3; i++) { 272 | if (i == 0) 273 | ch = ACS_LARROW; 274 | else 275 | ch = ACS_HLINE; 276 | mvwaddch(ctx->w, y, x + i, ch); 277 | } 278 | } 279 | 280 | if (q == ctx->vq_end - 1 && q != ctx->num_queues - 1) { 281 | for (i = 3; i-- > 0; ) { 282 | if (i == 0) 283 | ch = ACS_RARROW; 284 | else 285 | ch = ACS_HLINE; 286 | mvwaddch(ctx->w, y, x - i, ch); 287 | } 288 | } 289 | } 290 | 291 | static int bql_output_one(struct bql_ctx *ctx, unsigned int q) 292 | { 293 | struct bql_q_ctx *qctx = &ctx->queues[q]; 294 | return(bql_poll_one_queue(qctx)); 295 | } 296 | 297 | static void bql_draw_one(struct bql_ctx *ctx, unsigned int q) 298 | { 299 | struct bql_q_ctx *qctx = &ctx->queues[q]; 300 | unsigned int val, limit; 301 | unsigned int i; 302 | int color, rows; 303 | char buf[128]; 304 | int x; 305 | 306 | rows = ctx->rows; 307 | val = bql_poll_one_queue(qctx)/ctx->scaling; 308 | limit = ctx->queues[q].attrs[LIMIT].value/ctx->scaling; 309 | 310 | x = q * QUEUE_SPACING + QUEUE_VAL_X - ctx->x_start; 311 | 312 | snprintf(buf, sizeof(buf), "%02u", q); 313 | 314 | /* Draw the queue number */ 315 | wattron(ctx->w, COLOR_PAIR(5)); 316 | wattron(ctx->w, A_BOLD); 317 | mvwaddstr(ctx->w, rows - QUEUE_NUM_Y, q * QUEUE_SPACING + QUEUE_SEP_X - ctx->x_start, buf); 318 | wattroff(ctx->w, A_BOLD); 319 | wattroff(ctx->w, COLOR_PAIR(5)); 320 | 321 | /* Draw the queue value as a histogram */ 322 | for (i = 0; i < val; i++) { 323 | color = get_color_thresh(i, limit); 324 | wattron(ctx->w, COLOR_PAIR(color)); 325 | mvwaddch(ctx->w, rows - QUEUE_VAL_Y - i, x, QUEUE_CHAR | A_BOLD); 326 | wattroff(ctx->w, COLOR_PAIR(color)); 327 | } 328 | 329 | /* Display the queue limit value */ 330 | mvwaddch(ctx->w, rows - QUEUE_VAL_Y - limit, x, ACS_BLOCK); 331 | 332 | /* Display the arrows to indicate there is something */ 333 | bql_draw_arrows(ctx, q, limit); 334 | } 335 | 336 | static void bql_draw_main_items(struct bql_ctx *ctx) 337 | { 338 | int y = PARAMS_Y; 339 | 340 | box(ctx->w, 0, 0); 341 | 342 | wmove(ctx->w, y, PARAMS_X); 343 | waddstr(ctx->w, "Interface: "); 344 | wattron(ctx->w, A_BOLD); 345 | waddstr(ctx->w, ctx->iface); 346 | wattroff(ctx->w, A_BOLD); 347 | 348 | wmove(ctx->w, ++y, PARAMS_X); 349 | waddstr(ctx->w, "Frequency: "); 350 | wattron(ctx->w, A_BOLD); 351 | wprintw(ctx->w, "%d (msecs)", ctx->poll_freq); 352 | wattroff(ctx->w, A_BOLD); 353 | 354 | wmove(ctx->w, ++y, PARAMS_X); 355 | waddstr(ctx->w, "Driver: "); 356 | wattron(ctx->w, A_BOLD); 357 | wprintw(ctx->w, "%s (%s)", ctx->info.driver, ctx->info.version); 358 | wattroff(ctx->w, A_BOLD); 359 | 360 | /* Draw the separation line between queue number and values */ 361 | wmove(ctx->w, ++y, PARAMS_X); 362 | mvwhline(ctx->w, ctx->rows - QUEUE_SEP_Y, QUEUE_SEP_X, ACS_HLINE, 363 | ctx->h_line_val); 364 | 365 | y = PARAMS_Y; 366 | wattron(ctx->w, A_BOLD); 367 | mvwaddstr(ctx->w, y, ctx->version_x_pos, "BQLmon"); 368 | wattroff(ctx->w, A_BOLD); 369 | 370 | wmove(ctx->w, ++y, ctx->version_x_pos); 371 | waddstr(ctx->w, "Version: "); 372 | wattron(ctx->w, A_BOLD); 373 | wprintw(ctx->w, "%s", VERSION); 374 | wattroff(ctx->w, A_BOLD); 375 | 376 | wattron(ctx->w, A_BOLD); 377 | mvwaddstr(ctx->w, ++y, ctx->version_x_pos, "Q to exit"); 378 | wattroff(ctx->w, A_BOLD); 379 | } 380 | 381 | static void bql_recalc_visible_queues(struct bql_ctx *ctx) 382 | { 383 | unsigned int val; 384 | 385 | /* Get the number of times we need to repeat the ACS_HLINE */ 386 | val = ctx->num_queues * QUEUE_SPACING; 387 | if (val >= ctx->cols) 388 | val = ctx->cols - 2 * QUEUE_SEP_Y; 389 | ctx->h_line_val = val; 390 | 391 | ctx->vq_start = ctx->x_start / QUEUE_SPACING; 392 | ctx->vq_end = ctx->cols / QUEUE_SPACING + ctx->vq_start - 1; 393 | if (ctx->vq_end >= ctx->num_queues) 394 | ctx->vq_end = ctx->num_queues; 395 | } 396 | 397 | static void ts_add(struct timespec *t, uint64_t ns) 398 | { 399 | t->tv_nsec = t->tv_nsec + ns; 400 | if(t->tv_nsec >= 1000000000L) { 401 | t->tv_sec++; 402 | t->tv_nsec = t->tv_nsec - 1000000000L ; 403 | } 404 | } 405 | 406 | 407 | static void bql_output_loop(struct bql_ctx *ctx) 408 | { 409 | unsigned int q, exit = 0; 410 | uint64_t expirations = 0; 411 | uint64_t totals = 0; 412 | struct timespec time; 413 | 414 | read(ctx->timer,&expirations,sizeof(uint64_t)); 415 | clock_gettime(CLOCK_REALTIME, &time); 416 | q = ctx->monitor; 417 | 418 | while (!exit) { 419 | fprintf(ctx->fd,"%ld.%ld", time.tv_sec, time.tv_nsec); 420 | if(q >= 0 ) { 421 | int x = bql_output_one(ctx,q); 422 | fprintf(ctx->fd, " %d %d", 423 | ctx->queues[q].attrs[LIMIT].value, x); 424 | fprintf(ctx->fd,"\n"); 425 | } else { 426 | for (q = ctx->vq_start; q <= ctx->vq_end; q++) { 427 | int x = bql_output_one(ctx,q); 428 | fprintf(ctx->fd, " %d %d", 429 | ctx->queues[q].attrs[LIMIT].value, x); 430 | } 431 | q = -1; 432 | fprintf(ctx->fd,"\n"); 433 | } 434 | read(ctx->timer,&expirations,sizeof(uint64_t)); 435 | ts_add(&time, expirations * ctx->poll_freq * 1000000); 436 | if(ctx->count > 0) { 437 | totals += expirations; 438 | if(totals >= ctx->count) 439 | exit = 1; 440 | } 441 | } 442 | } 443 | 444 | static void bql_draw_loop(struct bql_ctx *ctx) 445 | { 446 | unsigned int q, exit = 0; 447 | int ch; 448 | uint64_t expirations; 449 | struct timespec time; 450 | read(ctx->timer,&expirations,sizeof(uint64_t)); 451 | clock_gettime(CLOCK_REALTIME, &time); 452 | 453 | while (!exit) { 454 | wclear(ctx->w); 455 | 456 | bql_draw_main_items(ctx); 457 | 458 | for (q = ctx->vq_start; q < ctx->vq_end; q++) 459 | bql_draw_one(ctx, q); 460 | 461 | ch = wgetch(ctx->w); 462 | switch (ch) { 463 | case 'Q': 464 | case 'q': 465 | exit = 1; 466 | break; 467 | 468 | case KEY_LEFT: 469 | if (ctx->x_start >= QUEUE_SPACING) 470 | ctx->x_start -= QUEUE_SPACING; 471 | break; 472 | 473 | case KEY_RIGHT: 474 | if (ctx->vq_end < ctx->num_queues) 475 | ctx->x_start += QUEUE_SPACING; 476 | break; 477 | 478 | case KEY_RESIZE: 479 | endwin(); 480 | getmaxyx(stdscr, ctx->rows, ctx->cols); 481 | break; 482 | 483 | default: 484 | read(ctx->timer,&expirations,sizeof(uint64_t)); 485 | ts_add(&time, expirations * ctx->poll_freq * 1000000); 486 | break; 487 | } 488 | 489 | bql_recalc_visible_queues(ctx); 490 | wrefresh(ctx->w); 491 | } 492 | } 493 | 494 | static int bql_init_term(struct bql_ctx *ctx) 495 | { 496 | int rows, cols; 497 | 498 | initscr(); 499 | getmaxyx(stdscr, rows, cols); 500 | cbreak(); 501 | noecho(); 502 | curs_set(0); 503 | 504 | ctx->rows = rows; 505 | ctx->cols = cols; 506 | ctx->x_start = 0; 507 | 508 | ctx->w = newwin(rows, cols, 0, 0); 509 | if (!ctx->w) { 510 | fprintf(stderr, "failed to create window\n"); 511 | return 1; 512 | } 513 | 514 | keypad(ctx->w, TRUE); 515 | nodelay(ctx->w, TRUE); 516 | 517 | start_color(); 518 | 519 | init_pair(1, COLOR_RED, COLOR_BLACK); 520 | init_pair(2, COLOR_GREEN, COLOR_BLACK); 521 | init_pair(3, COLOR_YELLOW, COLOR_BLACK); 522 | init_pair(4, COLOR_WHITE, COLOR_BLACK); 523 | init_pair(5, COLOR_WHITE, COLOR_BLUE); 524 | init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 525 | 526 | bql_recalc_visible_queues(ctx); 527 | 528 | ctx->version_x_pos = ctx->cols - strlen("Version: ") - 529 | strlen(VERSION) - QUEUE_SPACING; 530 | 531 | return 0; 532 | } 533 | 534 | static void usage(const char *pname) 535 | { 536 | fprintf(stderr, "Usage: %s [options]\n" 537 | "-i: interface\n" 538 | "-f: poll frequency (msecs)\n" 539 | "-s: scaling (higher means shorter bars, default 1024\n" 540 | "-o: output file or - for stdout\n" 541 | "-c: count (run for count * frequency) \n" 542 | "-Q: queue monitor specific BQL queue \n" 543 | "-h: this help\n", pname); 544 | } 545 | 546 | int main(int argc, char **argv) 547 | { 548 | struct bql_ctx *ctx; 549 | int opt, ret; 550 | struct itimerspec interval; 551 | 552 | ctx = calloc(sizeof(*ctx),1); 553 | if (!ctx) 554 | return -ENOMEM; 555 | 556 | ctx->monitor = -1; 557 | ctx->scaling = DEFAULT_SCALING; 558 | 559 | while ((opt = getopt(argc, argv, "c:Q:o:i:f:s:h")) > 0) { 560 | switch (opt) { 561 | case 'i': 562 | ctx->iface = optarg; 563 | break; 564 | case 'f': 565 | ctx->poll_freq = strtoul(optarg, 0, 10); 566 | break; 567 | case 's': 568 | ctx->scaling = strtoul(optarg, 0, 10); 569 | break; 570 | case 'c': 571 | ctx->count = strtoul(optarg, 0, 10); 572 | break; 573 | case 'o': 574 | ctx->filename = optarg; 575 | break; 576 | case 'Q': 577 | ctx->monitor = strtoul(optarg, 0, 10); 578 | break; 579 | default: 580 | usage(argv[0]); 581 | exit(EXIT_SUCCESS); 582 | break; 583 | } 584 | } 585 | 586 | argc -= optind; 587 | argv += optind; 588 | 589 | if (!ctx->iface) 590 | ctx->iface = "eth0"; 591 | 592 | if (!ctx->poll_freq) 593 | ctx->poll_freq = 10; 594 | 595 | if (ctx->filename) { 596 | if(strcmp(ctx->filename,"-") == 0) 597 | ctx->fd = stdout; 598 | else 599 | ctx->fd = fopen(ctx->filename,"w"); 600 | 601 | if(!ctx->fd) { 602 | usage(argv[0]); 603 | exit(EXIT_FAILURE); 604 | } 605 | } 606 | 607 | ret = bql_get_drv_info(ctx); 608 | if (ret) 609 | goto out; 610 | 611 | ret = bql_sysfs_init(ctx); 612 | if (ret) 613 | goto out; 614 | 615 | if (ctx->monitor >= 1 && ctx->num_queues - 1 > ctx->monitor) { 616 | fprintf(stderr, "Queue %d out of range\n", ctx->monitor); 617 | goto out; 618 | } 619 | 620 | ret = bql_queues_create(ctx); 621 | if (ret) 622 | goto out; 623 | 624 | interval.it_interval.tv_sec = 0; 625 | interval.it_interval.tv_nsec = ctx->poll_freq * 1000000; 626 | interval.it_value.tv_sec = 0; 627 | interval.it_value.tv_nsec = ctx->poll_freq * 1000000; 628 | 629 | ctx->timer = timerfd_create(CLOCK_MONOTONIC, 0); 630 | if (ctx->timer) { 631 | if (timerfd_settime(ctx->timer, 0, &interval, NULL) == -1) 632 | fprintf(stderr,("timerfd_settime")); 633 | } 634 | 635 | if(!ctx->filename) { 636 | ret = bql_init_term(ctx); 637 | if (ret) 638 | goto out_win; 639 | 640 | bql_draw_loop(ctx); 641 | } else { 642 | bql_output_loop(ctx); 643 | } 644 | 645 | out_win: 646 | endwin(); 647 | out: 648 | bql_queues_destroy(ctx); 649 | free(ctx); 650 | return 0; 651 | } 652 | -------------------------------------------------------------------------------- /bqlmon.h: -------------------------------------------------------------------------------- 1 | #ifndef __BQLMON_H 2 | #define __BQLMON_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | /* 10 | * 1:1 copy of the sysfs attributes 11 | */ 12 | struct bql_sysfs_attr { 13 | int fd; 14 | char s_val[512]; 15 | unsigned int value; 16 | }; 17 | 18 | enum bql_sysfs_attr_map { 19 | HOLD_TIME = 0, 20 | INFLIGHT, 21 | LIMIT, 22 | LIMIT_MAX, 23 | LIMIT_MIN, 24 | }; 25 | 26 | struct bql_q_ctx { 27 | unsigned int queue_num; 28 | struct bql_sysfs_attr attrs[5]; 29 | struct bql_ctx *g; 30 | }; 31 | 32 | struct bql_ctx { 33 | char *iface; 34 | char *filename; 35 | FILE *fd; 36 | struct ethtool_drvinfo info; 37 | unsigned int poll_freq; 38 | int timer; 39 | int count; 40 | int monitor; 41 | int scaling; 42 | unsigned int num_queues; 43 | unsigned int vq_start; 44 | unsigned int vq_end; 45 | struct bql_q_ctx *queues; 46 | WINDOW *w; 47 | int rows; 48 | int cols; 49 | unsigned int x_start; 50 | unsigned int h_line_val; 51 | unsigned int version_x_pos; 52 | }; 53 | 54 | /* Definitions for the graphical representation */ 55 | #define QUEUE_SPACING 3 /* px */ 56 | #define QUEUE_CHAR ACS_CKBOARD 57 | 58 | #define QUEUE_VAL_Y 4 59 | #define QUEUE_VAL_X 3 60 | 61 | #define QUEUE_SEP_Y 3 62 | #define QUEUE_SEP_X 2 63 | 64 | #define QUEUE_NUM_Y 2 65 | #define QUEUE_ARROW_Y 4 66 | 67 | #define PARAMS_X 3 68 | #define PARAMS_Y 2 69 | 70 | #define VERSION "0.1" 71 | 72 | #endif /* __BQLMON_H */ 73 | --------------------------------------------------------------------------------