osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 1 | /* lnstat.c: Unified linux network statistics |
| 2 | * |
| 3 | * Copyright (C) 2004 by Harald Welte <laforge@gnumonks.org> |
| 4 | * |
| 5 | * Development of this code was funded by Astaro AG, http://www.astaro.com/ |
| 6 | * |
| 7 | * Based on original concept and ideas from predecessor rtstat.c: |
| 8 | * |
| 9 | * Copyright 2001 by Robert Olsson <robert.olsson@its.uu.se> |
| 10 | * Uppsala University, Sweden |
| 11 | * |
| 12 | * This program is free software; you can redistribute it and/or modify |
| 13 | * it under the terms of the GNU General Public License as published by |
| 14 | * the Free Software Foundation; either version 2 of the License, or |
| 15 | * (at your option) any later version. |
| 16 | * |
| 17 | */ |
| 18 | |
| 19 | #include <unistd.h> |
| 20 | #include <stdio.h> |
| 21 | #include <stdlib.h> |
| 22 | #include <string.h> |
| 23 | #include <dirent.h> |
| 24 | #include <limits.h> |
| 25 | #include <time.h> |
| 26 | |
| 27 | #include <sys/time.h> |
| 28 | #include <sys/types.h> |
| 29 | |
| 30 | #include "lnstat.h" |
| 31 | |
| 32 | /* size of temp buffer used to read lines from procfiles */ |
| 33 | #define FGETS_BUF_SIZE 1024 |
| 34 | |
| 35 | |
| 36 | #define RTSTAT_COMPAT_LINE "entries in_hit in_slow_tot in_no_route in_brd in_martian_dst in_martian_src out_hit out_slow_tot out_slow_mc gc_total gc_ignored gc_goal_miss gc_dst_overflow in_hlist_search out_hlist_search\n" |
| 37 | |
| 38 | /* Read (and summarize for SMP) the different stats vars. */ |
| 39 | static int scan_lines(struct lnstat_file *lf, int i) |
| 40 | { |
Phil Sutter | db3ef44 | 2015-11-28 01:00:01 +0100 | [diff] [blame] | 41 | char buf[FGETS_BUF_SIZE]; |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 42 | int j, num_lines = 0; |
| 43 | |
| 44 | for (j = 0; j < lf->num_fields; j++) |
| 45 | lf->fields[j].values[i] = 0; |
| 46 | |
Phil Sutter | db3ef44 | 2015-11-28 01:00:01 +0100 | [diff] [blame] | 47 | rewind(lf->fp); |
| 48 | /* skip first line */ |
| 49 | if (!lf->compat && !fgets(buf, sizeof(buf)-1, lf->fp)) |
| 50 | return -1; |
| 51 | |
Stephen Hemminger | acd1e43 | 2016-03-21 11:56:36 -0700 | [diff] [blame] | 52 | while (!feof(lf->fp) && fgets(buf, sizeof(buf)-1, lf->fp)) { |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 53 | char *ptr = buf; |
| 54 | |
| 55 | num_lines++; |
| 56 | |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 57 | gettimeofday(&lf->last_read, NULL); |
| 58 | |
osdl.net!shemminger | f5f1a6c | 2005-03-18 19:40:19 +0000 | [diff] [blame] | 59 | for (j = 0; j < lf->num_fields; j++) { |
| 60 | unsigned long f = strtoul(ptr, &ptr, 16); |
Stephen Hemminger | acd1e43 | 2016-03-21 11:56:36 -0700 | [diff] [blame] | 61 | |
Stephen Hemminger | ae665a5 | 2006-12-05 10:10:22 -0800 | [diff] [blame] | 62 | if (j == 0) |
shemminger | 7339c0b | 2005-09-01 18:11:03 +0000 | [diff] [blame] | 63 | lf->fields[j].values[i] = f; |
osdl.net!shemminger | f5f1a6c | 2005-03-18 19:40:19 +0000 | [diff] [blame] | 64 | else |
| 65 | lf->fields[j].values[i] += f; |
| 66 | } |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 67 | } |
| 68 | return num_lines; |
| 69 | } |
| 70 | |
Stephen Hemminger | ae665a5 | 2006-12-05 10:10:22 -0800 | [diff] [blame] | 71 | static int time_after(struct timeval *last, |
| 72 | struct timeval *tout, |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 73 | struct timeval *now) |
| 74 | { |
| 75 | if (now->tv_sec > last->tv_sec + tout->tv_sec) |
| 76 | return 1; |
| 77 | |
| 78 | if (now->tv_sec == last->tv_sec + tout->tv_sec) { |
| 79 | if (now->tv_usec > last->tv_usec + tout->tv_usec) |
| 80 | return 1; |
| 81 | } |
| 82 | |
| 83 | return 0; |
| 84 | } |
| 85 | |
| 86 | int lnstat_update(struct lnstat_file *lnstat_files) |
| 87 | { |
| 88 | struct lnstat_file *lf; |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 89 | struct timeval tv; |
| 90 | |
| 91 | gettimeofday(&tv, NULL); |
| 92 | |
| 93 | for (lf = lnstat_files; lf; lf = lf->next) { |
| 94 | if (time_after(&lf->last_read, &lf->interval, &tv)) { |
| 95 | int i; |
| 96 | struct lnstat_field *lfi; |
| 97 | |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 98 | scan_lines(lf, 1); |
| 99 | |
Stephen Hemminger | ae665a5 | 2006-12-05 10:10:22 -0800 | [diff] [blame] | 100 | for (i = 0, lfi = &lf->fields[i]; |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 101 | i < lf->num_fields; i++, lfi = &lf->fields[i]) { |
| 102 | if (i == 0) |
| 103 | lfi->result = lfi->values[1]; |
| 104 | else |
| 105 | lfi->result = (lfi->values[1]-lfi->values[0]) |
Stephen Hemminger | acd1e43 | 2016-03-21 11:56:36 -0700 | [diff] [blame] | 106 | / lf->interval.tv_sec; |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 107 | } |
| 108 | |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 109 | scan_lines(lf, 0); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | return 0; |
| 114 | } |
| 115 | |
| 116 | /* scan first template line and fill in per-field data structures */ |
| 117 | static int __lnstat_scan_fields(struct lnstat_file *lf, char *buf) |
| 118 | { |
| 119 | char *tok; |
| 120 | int i; |
| 121 | |
| 122 | tok = strtok(buf, " \t\n"); |
| 123 | for (i = 0; i < LNSTAT_MAX_FIELDS_PER_LINE; i++) { |
| 124 | lf->fields[i].file = lf; |
| 125 | strncpy(lf->fields[i].name, tok, LNSTAT_MAX_FIELD_NAME_LEN); |
| 126 | /* has to be null-terminate since we initialize to zero |
| 127 | * and field size is NAME_LEN + 1 */ |
| 128 | tok = strtok(NULL, " \t\n"); |
| 129 | if (!tok) { |
| 130 | lf->num_fields = i+1; |
| 131 | return 0; |
| 132 | } |
| 133 | } |
| 134 | return 0; |
| 135 | } |
| 136 | |
| 137 | static int lnstat_scan_fields(struct lnstat_file *lf) |
| 138 | { |
| 139 | char buf[FGETS_BUF_SIZE]; |
| 140 | |
| 141 | rewind(lf->fp); |
Phil Sutter | d572ed4 | 2015-11-28 01:00:04 +0100 | [diff] [blame] | 142 | if (!fgets(buf, sizeof(buf)-1, lf->fp)) |
| 143 | return -1; |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 144 | |
| 145 | return __lnstat_scan_fields(lf, buf); |
| 146 | } |
| 147 | |
| 148 | /* fake function emulating lnstat_scan_fields() for old kernels */ |
| 149 | static int lnstat_scan_compat_rtstat_fields(struct lnstat_file *lf) |
| 150 | { |
| 151 | char buf[FGETS_BUF_SIZE]; |
| 152 | |
| 153 | strncpy(buf, RTSTAT_COMPAT_LINE, sizeof(buf)-1); |
| 154 | |
| 155 | return __lnstat_scan_fields(lf, buf); |
| 156 | } |
| 157 | |
| 158 | /* find out whether string 'name; is in given string array */ |
| 159 | static int name_in_array(const int num, const char **arr, const char *name) |
| 160 | { |
| 161 | int i; |
Stephen Hemminger | acd1e43 | 2016-03-21 11:56:36 -0700 | [diff] [blame] | 162 | |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 163 | for (i = 0; i < num; i++) { |
| 164 | if (!strcmp(arr[i], name)) |
| 165 | return 1; |
| 166 | } |
| 167 | return 0; |
| 168 | } |
| 169 | |
| 170 | /* allocate lnstat_file and open given file */ |
| 171 | static struct lnstat_file *alloc_and_open(const char *path, const char *file) |
| 172 | { |
| 173 | struct lnstat_file *lf; |
| 174 | |
| 175 | /* allocate */ |
Phil Sutter | f89bb02 | 2016-07-18 16:48:43 +0200 | [diff] [blame] | 176 | lf = calloc(1, sizeof(*lf)); |
Stephen Hemminger | b90b773 | 2015-12-30 17:28:11 -0800 | [diff] [blame] | 177 | if (!lf) { |
| 178 | fprintf(stderr, "out of memory\n"); |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 179 | return NULL; |
Stephen Hemminger | b90b773 | 2015-12-30 17:28:11 -0800 | [diff] [blame] | 180 | } |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 181 | |
| 182 | /* initialize */ |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 183 | /* de->d_name is guaranteed to be <= NAME_MAX */ |
| 184 | strcpy(lf->basename, file); |
| 185 | strcpy(lf->path, path); |
| 186 | strcat(lf->path, "/"); |
| 187 | strcat(lf->path, lf->basename); |
| 188 | |
| 189 | /* initialize to default */ |
| 190 | lf->interval.tv_sec = 1; |
| 191 | |
| 192 | /* open */ |
| 193 | lf->fp = fopen(lf->path, "r"); |
| 194 | if (!lf->fp) { |
Stephen Hemminger | b90b773 | 2015-12-30 17:28:11 -0800 | [diff] [blame] | 195 | perror(lf->path); |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 196 | free(lf); |
| 197 | return NULL; |
| 198 | } |
| 199 | |
| 200 | return lf; |
| 201 | } |
| 202 | |
| 203 | |
| 204 | /* lnstat_scan_dir - find and parse all available statistics files/fields */ |
| 205 | struct lnstat_file *lnstat_scan_dir(const char *path, const int num_req_files, |
| 206 | const char **req_files) |
| 207 | { |
| 208 | DIR *dir; |
| 209 | struct lnstat_file *lnstat_files = NULL; |
| 210 | struct dirent *de; |
| 211 | |
| 212 | if (!path) |
| 213 | path = PROC_NET_STAT; |
Stephen Hemminger | ae665a5 | 2006-12-05 10:10:22 -0800 | [diff] [blame] | 214 | |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 215 | dir = opendir(path); |
| 216 | if (!dir) { |
| 217 | struct lnstat_file *lf; |
| 218 | /* Old kernel, before /proc/net/stat was introduced */ |
| 219 | fprintf(stderr, "Your kernel doesn't have lnstat support. "); |
| 220 | |
| 221 | /* we only support rtstat, not multiple files */ |
| 222 | if (num_req_files >= 2) { |
| 223 | fputc('\n', stderr); |
| 224 | return NULL; |
| 225 | } |
| 226 | |
| 227 | /* we really only accept rt_cache */ |
| 228 | if (num_req_files && !name_in_array(num_req_files, |
| 229 | req_files, "rt_cache")) { |
| 230 | fputc('\n', stderr); |
| 231 | return NULL; |
| 232 | } |
| 233 | |
| 234 | fprintf(stderr, "Fallback to old rtstat-only operation\n"); |
| 235 | |
| 236 | lf = alloc_and_open("/proc/net", "rt_cache_stat"); |
| 237 | if (!lf) |
| 238 | return NULL; |
| 239 | lf->compat = 1; |
| 240 | strncpy(lf->basename, "rt_cache", sizeof(lf->basename)); |
| 241 | |
| 242 | /* FIXME: support for old files */ |
| 243 | if (lnstat_scan_compat_rtstat_fields(lf) < 0) |
| 244 | return NULL; |
| 245 | |
| 246 | lf->next = lnstat_files; |
| 247 | lnstat_files = lf; |
| 248 | return lnstat_files; |
| 249 | } |
| 250 | |
| 251 | while ((de = readdir(dir))) { |
| 252 | struct lnstat_file *lf; |
| 253 | |
| 254 | if (de->d_type != DT_REG) |
| 255 | continue; |
| 256 | |
| 257 | if (num_req_files && !name_in_array(num_req_files, |
| 258 | req_files, de->d_name)) |
| 259 | continue; |
| 260 | |
| 261 | lf = alloc_and_open(path, de->d_name); |
Stephen Hemminger | b90b773 | 2015-12-30 17:28:11 -0800 | [diff] [blame] | 262 | if (!lf) { |
| 263 | closedir(dir); |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 264 | return NULL; |
Stephen Hemminger | b90b773 | 2015-12-30 17:28:11 -0800 | [diff] [blame] | 265 | } |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 266 | |
| 267 | /* fill in field structure */ |
Stephen Hemminger | b90b773 | 2015-12-30 17:28:11 -0800 | [diff] [blame] | 268 | if (lnstat_scan_fields(lf) < 0) { |
| 269 | closedir(dir); |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 270 | return NULL; |
Stephen Hemminger | b90b773 | 2015-12-30 17:28:11 -0800 | [diff] [blame] | 271 | } |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 272 | |
| 273 | /* prepend to global list */ |
| 274 | lf->next = lnstat_files; |
| 275 | lnstat_files = lf; |
| 276 | } |
| 277 | closedir(dir); |
| 278 | |
| 279 | return lnstat_files; |
| 280 | } |
| 281 | |
| 282 | int lnstat_dump(FILE *outfd, struct lnstat_file *lnstat_files) |
| 283 | { |
| 284 | struct lnstat_file *lf; |
| 285 | |
| 286 | for (lf = lnstat_files; lf; lf = lf->next) { |
| 287 | int i; |
| 288 | |
| 289 | fprintf(outfd, "%s:\n", lf->path); |
| 290 | |
| 291 | for (i = 0; i < lf->num_fields; i++) |
| 292 | fprintf(outfd, "\t%2u: %s\n", i+1, lf->fields[i].name); |
| 293 | |
| 294 | } |
| 295 | return 0; |
| 296 | } |
| 297 | |
| 298 | struct lnstat_field *lnstat_find_field(struct lnstat_file *lnstat_files, |
| 299 | const char *name) |
| 300 | { |
| 301 | struct lnstat_file *lf; |
| 302 | struct lnstat_field *ret = NULL; |
| 303 | const char *colon = strchr(name, ':'); |
| 304 | char *file; |
| 305 | const char *field; |
| 306 | |
| 307 | if (colon) { |
| 308 | file = strndup(name, colon-name); |
| 309 | field = colon+1; |
| 310 | } else { |
| 311 | file = NULL; |
| 312 | field = name; |
| 313 | } |
Stephen Hemminger | ae665a5 | 2006-12-05 10:10:22 -0800 | [diff] [blame] | 314 | |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 315 | for (lf = lnstat_files; lf; lf = lf->next) { |
| 316 | int i; |
| 317 | |
| 318 | if (file && strcmp(file, lf->basename)) |
| 319 | continue; |
| 320 | |
| 321 | for (i = 0; i < lf->num_fields; i++) { |
| 322 | if (!strcmp(field, lf->fields[i].name)) { |
| 323 | ret = &lf->fields[i]; |
| 324 | goto out; |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | out: |
Stephen Hemminger | 92de1c2 | 2015-07-21 10:49:54 -0700 | [diff] [blame] | 329 | free(file); |
osdl.net!shemminger | 4677a54 | 2004-10-19 20:47:13 +0000 | [diff] [blame] | 330 | |
| 331 | return ret; |
| 332 | } |