ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/libstatgrab/src/statgrab/statgrab.c
(Generate patch)

Comparing projects/libstatgrab/src/statgrab/statgrab.c (file contents):
Revision 1.13 by ats, Mon Nov 10 23:35:43 2003 UTC vs.
Revision 1.37 by ats, Fri Mar 17 13:23:05 2006 UTC

# Line 1 | Line 1
1   /*
2 < * i-scream central monitoring system
2 > * i-scream libstatgrab
3   * http://www.i-scream.org
4 < * Copyright (C) 2000-2003 i-scream
4 > * Copyright (C) 2000-2004 i-scream
5   *
6   * This program is free software; you can redistribute it and/or
7   * modify it under the terms of the GNU General Public License
# Line 16 | Line 16
16   * You should have received a copy of the GNU General Public License
17   * along with this program; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 + *
20 + * $Id$
21   */
22  
23   #ifdef HAVE_CONFIG_H
# Line 31 | Line 33
33  
34   typedef enum {
35          LONG_LONG = 0,
36 +        BYTES,
37          TIME_T,
38          FLOAT,
39          DOUBLE,
40          STRING,
41 <        INT
41 >        INT,
42 >        BOOL,
43 >        DUPLEX
44   } stat_type;
45  
46   typedef enum {
# Line 67 | Line 72 | repeat_mode_type repeat_mode = REPEAT_NONE;
72   int repeat_time = 1;
73   int use_cpu_percent = 0;
74   int use_diffs = 0;
75 + long float_scale_factor = 0;
76 + long long bytes_scale_factor = 0;
77  
78   /* Exit with an error message. */
79   void die(const char *s) {
# Line 114 | Line 121 | void add_stat(stat_type type, void *stat, ...) {
121                          break;
122                  partlen = strlen(part);
123                  memcpy(p, part, partlen);
124 <                p += partlen;
124 >
125 >                /* Replace spaces and dots with underscores. */
126 >                while (partlen-- > 0) {
127 >                        if (*p == ' ' || *p == '.')
128 >                                *p = '_';
129 >                        p++;
130 >                }
131 >
132                  *p++ = '.';
133          }
134          va_end(ap);
135          *--p = '\0';
136  
123        /* Replace spaces with underscores. */
124        for (p = name; *p != '\0'; p++) {
125                if (*p == ' ')
126                        *p = '_';
127        }
128
137          /* Stretch the stats array if necessary. */
138          if (num_stats >= alloc_stats) {
139                  alloc_stats += INCREMENT_STATS;
# Line 162 | Line 170 | void populate_const() {
170  
171   void populate_cpu() {
172          if (use_cpu_percent) {
173 <                cpu_percent_t *cpu_p = cpu_percent_usage();
173 >                sg_cpu_percents *cpu_p = sg_get_cpu_percents();
174  
175                  if (cpu_p != NULL) {
176                          add_stat(FLOAT, &cpu_p->user,
177 <                                 "cpu", "user", NULL);
177 >                                 "cpu", "user", NULL);
178                          add_stat(FLOAT, &cpu_p->kernel,
179 <                                 "cpu", "kernel", NULL);
179 >                                 "cpu", "kernel", NULL);
180                          add_stat(FLOAT, &cpu_p->idle,
181 <                                 "cpu", "idle", NULL);
181 >                                 "cpu", "idle", NULL);
182                          add_stat(FLOAT, &cpu_p->iowait,
183 <                                 "cpu", "iowait", NULL);
183 >                                 "cpu", "iowait", NULL);
184                          add_stat(FLOAT, &cpu_p->swap,
185 <                                 "cpu", "swap", NULL);
185 >                                 "cpu", "swap", NULL);
186                          add_stat(FLOAT, &cpu_p->nice,
187 <                                 "cpu", "nice", NULL);
187 >                                 "cpu", "nice", NULL);
188                          add_stat(TIME_T, &cpu_p->time_taken,
189 <                                 "cpu", "time_taken", NULL);
189 >                                 "cpu", "time_taken", NULL);
190                  }
191          } else {
192 <                cpu_states_t *cpu_s;
192 >                sg_cpu_stats *cpu_s;
193  
194 <                cpu_s = use_diffs ? get_cpu_diff() : get_cpu_totals();
194 >                cpu_s = use_diffs ? sg_get_cpu_stats_diff()
195 >                                  : sg_get_cpu_stats();
196                  if (cpu_s != NULL) {
197                          add_stat(LONG_LONG, &cpu_s->user,
198 <                                 "cpu", "user", NULL);
198 >                                 "cpu", "user", NULL);
199                          add_stat(LONG_LONG, &cpu_s->kernel,
200 <                                 "cpu", "kernel", NULL);
200 >                                 "cpu", "kernel", NULL);
201                          add_stat(LONG_LONG, &cpu_s->idle,
202 <                                 "cpu", "idle", NULL);
202 >                                 "cpu", "idle", NULL);
203                          add_stat(LONG_LONG, &cpu_s->iowait,
204 <                                 "cpu", "iowait", NULL);
204 >                                 "cpu", "iowait", NULL);
205                          add_stat(LONG_LONG, &cpu_s->swap,
206 <                                 "cpu", "swap", NULL);
206 >                                 "cpu", "swap", NULL);
207                          add_stat(LONG_LONG, &cpu_s->nice,
208 <                                 "cpu", "nice", NULL);
208 >                                 "cpu", "nice", NULL);
209                          add_stat(LONG_LONG, &cpu_s->total,
210 <                                 "cpu", "total", NULL);
210 >                                 "cpu", "total", NULL);
211                          add_stat(TIME_T, &cpu_s->systime,
212 <                                 "cpu", "systime", NULL);
212 >                                 "cpu", "systime", NULL);
213                  }
214          }
215   }
216  
217   void populate_mem() {
218 <        mem_stat_t *mem = get_memory_stats();
218 >        sg_mem_stats *mem = sg_get_mem_stats();
219  
220          if (mem != NULL) {
221 <                add_stat(LONG_LONG, &mem->total, "mem", "total", NULL);
222 <                add_stat(LONG_LONG, &mem->free, "mem", "free", NULL);
223 <                add_stat(LONG_LONG, &mem->used, "mem", "used", NULL);
224 <                add_stat(LONG_LONG, &mem->cache, "mem", "cache", NULL);
221 >                add_stat(BYTES, &mem->total, "mem", "total", NULL);
222 >                add_stat(BYTES, &mem->free, "mem", "free", NULL);
223 >                add_stat(BYTES, &mem->used, "mem", "used", NULL);
224 >                add_stat(BYTES, &mem->cache, "mem", "cache", NULL);
225          }
226   }
227  
228   void populate_load() {
229 <        load_stat_t *load = get_load_stats();
229 >        sg_load_stats *load = sg_get_load_stats();
230  
231          if (load != NULL) {
232                  add_stat(DOUBLE, &load->min1, "load", "min1", NULL);
# Line 227 | Line 236 | void populate_load() {
236   }
237  
238   void populate_user() {
239 <        user_stat_t *user = get_user_stats();
239 >        sg_user_stats *user = sg_get_user_stats();
240  
241          if (user != NULL) {
242                  add_stat(INT, &user->num_entries, "user", "num", NULL);
# Line 236 | Line 245 | void populate_user() {
245   }
246  
247   void populate_swap() {
248 <        swap_stat_t *swap = get_swap_stats();
248 >        sg_swap_stats *swap = sg_get_swap_stats();
249  
250          if (swap != NULL) {
251 <                add_stat(LONG_LONG, &swap->total, "swap", "total", NULL);
252 <                add_stat(LONG_LONG, &swap->used, "swap", "used", NULL);
253 <                add_stat(LONG_LONG, &swap->free, "swap", "free", NULL);
251 >                add_stat(BYTES, &swap->total, "swap", "total", NULL);
252 >                add_stat(BYTES, &swap->used, "swap", "used", NULL);
253 >                add_stat(BYTES, &swap->free, "swap", "free", NULL);
254          }
255   }
256  
257   void populate_general() {
258 <        general_stat_t *gen = get_general_stats();
258 >        /* FIXME this should be renamed to host. */
259 >        sg_host_info *host = sg_get_host_info();
260  
261 <        if (gen != NULL) {
262 <                add_stat(STRING, &gen->os_name,
263 <                         "general", "os_name", NULL);
264 <                add_stat(STRING, &gen->os_release,
265 <                         "general", "os_release", NULL);
266 <                add_stat(STRING, &gen->os_version,
267 <                         "general", "os_version", NULL);
268 <                add_stat(STRING, &gen->platform, "general", "platform", NULL);
269 <                add_stat(STRING, &gen->hostname, "general", "hostname", NULL);
270 <                add_stat(TIME_T, &gen->uptime, "general", "uptime", NULL);
261 >        if (host != NULL) {
262 >                add_stat(STRING, &host->os_name,
263 >                         "general", "os_name", NULL);
264 >                add_stat(STRING, &host->os_release,
265 >                         "general", "os_release", NULL);
266 >                add_stat(STRING, &host->os_version,
267 >                         "general", "os_version", NULL);
268 >                add_stat(STRING, &host->platform, "general", "platform", NULL);
269 >                add_stat(STRING, &host->hostname, "general", "hostname", NULL);
270 >                add_stat(TIME_T, &host->uptime, "general", "uptime", NULL);
271          }
272   }
273  
274   void populate_fs() {
275          int n, i;
276 <        disk_stat_t *disk = get_disk_stats(&n);
276 >        sg_fs_stats *disk = sg_get_fs_stats(&n);
277  
278          if (disk != NULL) {
279                  for (i = 0; i < n; i++) {
# Line 287 | Line 297 | void populate_fs() {
297                                  *p = '_';
298  
299                          add_stat(STRING, &disk[i].device_name,
300 <                                 "fs", name, "device_name", NULL);
300 >                                 "fs", name, "device_name", NULL);
301                          add_stat(STRING, &disk[i].fs_type,
302 <                                 "fs", name, "fs_type", NULL);
302 >                                 "fs", name, "fs_type", NULL);
303                          add_stat(STRING, &disk[i].mnt_point,
304 <                                 "fs", name, "mnt_point", NULL);
305 <                        add_stat(LONG_LONG, &disk[i].size,
306 <                                 "fs", name, "size", NULL);
307 <                        add_stat(LONG_LONG, &disk[i].used,
308 <                                 "fs", name, "used", NULL);
309 <                        add_stat(LONG_LONG, &disk[i].avail,
310 <                                 "fs", name, "avail", NULL);
304 >                                 "fs", name, "mnt_point", NULL);
305 >                        add_stat(BYTES, &disk[i].size,
306 >                                 "fs", name, "size", NULL);
307 >                        add_stat(BYTES, &disk[i].used,
308 >                                 "fs", name, "used", NULL);
309 >                        add_stat(BYTES, &disk[i].avail,
310 >                                 "fs", name, "avail", NULL);
311                          add_stat(LONG_LONG, &disk[i].total_inodes,
312 <                                 "fs", name, "total_inodes", NULL);
312 >                                 "fs", name, "total_inodes", NULL);
313                          add_stat(LONG_LONG, &disk[i].used_inodes,
314 <                                 "fs", name, "used_inodes", NULL);
314 >                                 "fs", name, "used_inodes", NULL);
315                          add_stat(LONG_LONG, &disk[i].free_inodes,
316 <                                 "fs", name, "free_inodes", NULL);
316 >                                 "fs", name, "free_inodes", NULL);
317 >                        add_stat(LONG_LONG, &disk[i].avail_inodes,
318 >                                 "fs", name, "avail_inodes", NULL);
319 >                        add_stat(LONG_LONG, &disk[i].io_size,
320 >                                 "fs", name, "io_size", NULL);
321 >                        add_stat(LONG_LONG, &disk[i].block_size,
322 >                                 "fs", name, "block_size", NULL);
323 >                        add_stat(LONG_LONG, &disk[i].total_blocks,
324 >                                 "fs", name, "total_blocks", NULL);
325 >                        add_stat(LONG_LONG, &disk[i].free_blocks,
326 >                                 "fs", name, "free_blocks", NULL);
327 >                        add_stat(LONG_LONG, &disk[i].avail_blocks,
328 >                                 "fs", name, "avail_blocks", NULL);
329 >                        add_stat(LONG_LONG, &disk[i].used_blocks,
330 >                                 "fs", name, "used_blocks", NULL);
331  
332                          free(buf);
333                  }
# Line 312 | Line 336 | void populate_fs() {
336  
337   void populate_disk() {
338          int n, i;
339 <        diskio_stat_t *diskio;
339 >        sg_disk_io_stats *diskio;
340  
341 <        diskio = use_diffs ? get_diskio_stats_diff(&n) : get_diskio_stats(&n);
341 >        diskio = use_diffs ? sg_get_disk_io_stats_diff(&n)
342 >                           : sg_get_disk_io_stats(&n);
343          if (diskio != NULL) {
344                  for (i = 0; i < n; i++) {
345                          const char *name = diskio[i].disk_name;
346          
347                          add_stat(STRING, &diskio[i].disk_name,
348 <                                 "disk", name, "disk_name", NULL);
349 <                        add_stat(LONG_LONG, &diskio[i].read_bytes,
350 <                                 "disk", name, "read_bytes", NULL);
351 <                        add_stat(LONG_LONG, &diskio[i].write_bytes,
352 <                                 "disk", name, "write_bytes", NULL);
348 >                                 "disk", name, "disk_name", NULL);
349 >                        add_stat(BYTES, &diskio[i].read_bytes,
350 >                                 "disk", name, "read_bytes", NULL);
351 >                        add_stat(BYTES, &diskio[i].write_bytes,
352 >                                 "disk", name, "write_bytes", NULL);
353                          add_stat(TIME_T, &diskio[i].systime,
354 <                                 "disk", name, "systime", NULL);
354 >                                 "disk", name, "systime", NULL);
355                  }
356          }
357   }
358  
359   void populate_proc() {
360 <        process_stat_t *proc = get_process_stats();
360 >        /* FIXME expose individual process info too */
361 >        sg_process_count *proc = sg_get_process_count();
362  
363          if (proc != NULL) {
364                  add_stat(INT, &proc->total, "proc", "total", NULL);
# Line 344 | Line 370 | void populate_proc() {
370   }
371  
372   void populate_net() {
373 <        int n, i;
374 <        network_stat_t *net;
373 >        int num_io, num_iface, i;
374 >        sg_network_io_stats *io;
375 >        sg_network_iface_stats *iface;
376  
377 <        net = use_diffs ? get_network_stats_diff(&n) : get_network_stats(&n);
378 <        if (net != NULL) {
379 <                for (i = 0; i < n; i++) {
380 <                        const char *name = net[i].interface_name;
377 >        io = use_diffs ? sg_get_network_io_stats_diff(&num_io)
378 >                       : sg_get_network_io_stats(&num_io);
379 >        if (io != NULL) {
380 >                for (i = 0; i < num_io; i++) {
381 >                        const char *name = io[i].interface_name;
382          
383 <                        add_stat(STRING, &net[i].interface_name,
384 <                                 "net", name, "interface_name", NULL);
385 <                        add_stat(LONG_LONG, &net[i].tx,
386 <                                 "net", name, "tx", NULL);
387 <                        add_stat(LONG_LONG, &net[i].rx,
388 <                                 "net", name, "rx", NULL);
389 <                        add_stat(TIME_T, &net[i].systime,
390 <                                 "net", name, "systime", NULL);
383 >                        add_stat(STRING, &io[i].interface_name,
384 >                                 "net", name, "interface_name", NULL);
385 >                        add_stat(BYTES, &io[i].tx,
386 >                                 "net", name, "tx", NULL);
387 >                        add_stat(BYTES, &io[i].rx,
388 >                                 "net", name, "rx", NULL);
389 >                        add_stat(LONG_LONG, &io[i].ipackets,
390 >                                 "net", name, "ipackets", NULL);
391 >                        add_stat(LONG_LONG, &io[i].opackets,
392 >                                 "net", name, "opackets", NULL);
393 >                        add_stat(LONG_LONG, &io[i].ierrors,
394 >                                 "net", name, "ierrors", NULL);
395 >                        add_stat(LONG_LONG, &io[i].oerrors,
396 >                                 "net", name, "oerrors", NULL);
397 >                        add_stat(LONG_LONG, &io[i].collisions,
398 >                                 "net", name, "collisions", NULL);
399 >                        add_stat(TIME_T, &io[i].systime,
400 >                                 "net", name, "systime", NULL);
401                  }
402          }
403 +
404 +        iface = sg_get_network_iface_stats(&num_iface);
405 +        if (iface != NULL) {
406 +                for (i = 0; i < num_iface; i++) {
407 +                        const char *name = iface[i].interface_name;
408 +                        int had_io = 0, j;
409 +
410 +                        /* If there wasn't a corresponding io stat,
411 +                           add interface_name from here. */
412 +                        if (io != NULL) {
413 +                                for (j = 0; j < num_io; j++) {
414 +                                        if (strcmp(io[j].interface_name,
415 +                                                   name) == 0) {
416 +                                                had_io = 1;
417 +                                                break;
418 +                                        }
419 +                                }
420 +                        }
421 +                        if (!had_io) {
422 +                                add_stat(STRING, &iface[i].interface_name,
423 +                                        "net", name, "interface_name", NULL);
424 +                        }
425 +
426 +                        add_stat(INT, &iface[i].speed,
427 +                                 "net", name, "speed", NULL);
428 +                        add_stat(BOOL, &iface[i].up,
429 +                                 "net", name, "up", NULL);
430 +                        add_stat(DUPLEX, &iface[i].duplex,
431 +                                 "net", name, "duplex", NULL);
432 +                }
433 +        }
434   }
435  
436   void populate_page() {
437 <        page_stat_t *page;
437 >        sg_page_stats *page;
438  
439 <        page = use_diffs ? get_page_stats_diff() : get_page_stats();
439 >        page = use_diffs ? sg_get_page_stats_diff() : sg_get_page_stats();
440          if (page != NULL) {
441                  add_stat(LONG_LONG, &page->pages_pagein, "page", "in", NULL);
442                  add_stat(LONG_LONG, &page->pages_pageout, "page", "out", NULL);
# Line 410 | Line 479 | void select_interesting(int argc, char **argv) {
479                  for (i = 0; i < argc; i++) {
480                          for (t = &toplevels[0]; t->name != NULL; t++) {
481                                  if (strncmp(argv[i], t->name,
482 <                                            strlen(t->name)) == 0) {
482 >                                            strlen(t->name)) == 0) {
483                                          t->interesting = 1;
484                                          break;
485                                  }
# Line 430 | Line 499 | void get_stats() {
499                          t->populate();
500          }
501  
502 <        qsort(stats, num_stats, sizeof *stats, stats_compare);
502 >        if (stats != NULL)
503 >                qsort(stats, num_stats, sizeof *stats, stats_compare);
504   }
505  
506   /* Print the value of a stat. */
507   void print_stat_value(const stat *s) {
508          void *v = s->stat;
509 +        double fv;
510 +        long lv;
511 +        long long llv;
512  
513          switch (s->type) {
514          case LONG_LONG:
515 + #ifdef WIN32 /* Windows printf does not understand %lld, so use %I64d instead */
516 +                printf("%I64d", *(long long *)v);
517 + #else
518                  printf("%lld", *(long long *)v);
519 + #endif
520                  break;
521 +        case BYTES:
522 +                llv = *(long long *)v;
523 +                if (bytes_scale_factor != 0) {
524 +                        llv /= bytes_scale_factor;
525 +                }
526 + #ifdef WIN32
527 +                printf("%I64d", llv);
528 + #else
529 +                printf("%lld", llv);
530 + #endif
531 +                break;
532          case TIME_T:
533                  /* FIXME option for formatted time? */
534 <                printf("%ld", *(time_t *)v);
534 >                lv = *(time_t *)v;
535 >                printf("%ld", lv);
536                  break;
537          case FLOAT:
449                printf("%f", *(float *)v);
450                break;
538          case DOUBLE:
539 <                printf("%f", *(double *)v);
539 >                if (s->type == FLOAT) {
540 >                        fv = *(float *)v;
541 >                } else {
542 >                        fv = *(double *)v;
543 >                }
544 >                if (float_scale_factor != 0) {
545 >                        printf("%ld", (long)(float_scale_factor * fv));
546 >                } else {
547 >                        printf("%f", fv);
548 >                }
549                  break;
550          case STRING:
551                  /* FIXME escaping? */
# Line 458 | Line 554 | void print_stat_value(const stat *s) {
554          case INT:
555                  printf("%d", *(int *)v);
556                  break;
557 +        case BOOL:
558 +                printf("%s", *(int *)v ? "true" : "false");
559 +                break;
560 +        case DUPLEX:
561 +                switch (*(sg_iface_duplex *) v) {
562 +                case SG_IFACE_DUPLEX_FULL:
563 +                        printf("full");
564 +                        break;
565 +                case SG_IFACE_DUPLEX_HALF:
566 +                        printf("half");
567 +                        break;
568 +                default:
569 +                        printf("unknown");
570 +                        break;
571 +                }
572 +                break;
573          }
574   }
575  
# Line 500 | Line 612 | void print_stats(int argc, char **argv) {
612                          else
613                                  compare = stats_compare;
614  
615 <                        s = (const stat *)bsearch(&key, stats, num_stats,
616 <                                                  sizeof *stats, compare);
615 >                        if (stats == NULL) {
616 >                                s = NULL;
617 >                        } else {
618 >                                s = (const stat *)bsearch(&key, stats,
619 >                                                          num_stats,
620 >                                                          sizeof *stats,
621 >                                                          compare);
622 >                        }
623 >
624                          if (s == NULL) {
625                                  printf("Unknown stat %s\n", name);
626                                  continue;
# Line 543 | Line 662 | void usage() {
662                 "  -t DELAY   When repeating, wait DELAY seconds between updates (default 1)\n"
663                 "  -p         Display CPU usage differences as percentages rather than\n"
664                 "             absolute values\n"
665 +               "  -f FACTOR  Display floating-point values as integers scaled by FACTOR\n"
666 +               "  -K         Display byte counts in kibibytes\n"
667 +               "  -M         Display byte counts in mebibytes\n"
668 +               "  -G         Display byte counts in gibibytes\n"
669                 "\n");
670          printf("Version %s - report bugs to <%s>.\n",
671                 PACKAGE_VERSION, PACKAGE_BUGREPORT);
# Line 552 | Line 675 | void usage() {
675   int main(int argc, char **argv) {
676          opterr = 0;
677          while (1) {
678 <                int c = getopt(argc, argv, "lbmunsot:p");
678 >                int c = getopt(argc, argv, "lbmunsot:pf:KMG");
679                  if (c == -1)
680                          break;
681                  switch (c) {
# Line 583 | Line 706 | int main(int argc, char **argv) {
706                  case 'p':
707                          use_cpu_percent = 1;
708                          break;
709 +                case 'f':
710 +                        float_scale_factor = atol(optarg);
711 +                        break;
712 +                case 'K':
713 +                        bytes_scale_factor = 1024;
714 +                        break;
715 +                case 'M':
716 +                        bytes_scale_factor = 1024 * 1024;
717 +                        break;
718 +                case 'G':
719 +                        bytes_scale_factor = 1024 * 1024 * 1024;
720 +                        break;
721                  default:
722                          usage();
723                  }
# Line 605 | Line 740 | int main(int argc, char **argv) {
740  
741          select_interesting(argc - optind, &argv[optind]);
742  
743 <        /* We don't care if statgrab_init fails, because we can just display
743 >        /* We don't care if sg_init fails, because we can just display
744             the statistics that can be read as non-root. */
745 <        statgrab_init();
746 < #ifdef ALLBSD
747 <        if (setegid(getgid()) != 0)
748 <                die("Failed to lose effective group");
614 < #endif
745 >        sg_init();
746 >        sg_snapshot();
747 >        if (sg_drop_privileges() != 0)
748 >                die("Failed to drop setuid/setgid privileges");
749  
750          switch (repeat_mode) {
751          case REPEAT_NONE:
# Line 621 | Line 755 | int main(int argc, char **argv) {
755          case REPEAT_ONCE:
756                  get_stats();
757                  sleep(repeat_time);
758 +                sg_snapshot();
759                  get_stats();
760                  print_stats(argc, argv);
761                  break;
# Line 630 | Line 765 | int main(int argc, char **argv) {
765                          print_stats(argc, argv);
766                          printf("\n");
767                          sleep(repeat_time);
768 +                        sg_snapshot();
769                  }
770          }
771  
# Line 637 | Line 773 | int main(int argc, char **argv) {
773                  printf("\n");
774                  printf("statgrab\n");
775          }
776 +
777 +        sg_shutdown();
778  
779          return 0;
780   }

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines