1 /**
2 * collectd - src/nfs.c
3 * Copyright (C) 2005,2006 Jason Pepas
4 * Copyright (C) 2012,2013 Florian Forster
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; only version 2 of the License is applicable.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Authors:
20 * Jason Pepas <cell at ices.utexas.edu>
21 * Florian octo Forster <octo at collectd.org>
22 * Cosmin Ioiart <cioiart at gmail.com>
23 **/
25 #include "collectd.h"
27 #include "common.h"
28 #include "plugin.h"
30 #if HAVE_KSTAT_H
31 #include <kstat.h>
32 #endif
34 static const char *config_keys[] = {"ReportV2", "ReportV3", "ReportV4"};
35 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
36 static _Bool report_v2 = 1;
37 static _Bool report_v3 = 1;
38 static _Bool report_v4 = 1;
40 /*
41 see /proc/net/rpc/nfs
42 see http://www.missioncriticallinux.com/orph/NFS-Statistics
44 net x x x x
45 rpc_stat.netcnt Not used; always zero.
46 rpc_stat.netudpcnt Not used; always zero.
47 rpc_stat.nettcpcnt Not used; always zero.
48 rpc_stat.nettcpconn Not used; always zero.
50 rpc x x x
51 rpc_stat.rpccnt The number of RPC calls.
52 rpc_stat.rpcretrans The number of retransmitted RPC calls.
53 rpc_stat.rpcauthrefresh The number of credential refreshes.
55 proc2 x x x...
56 proc3 x x x...
58 Procedure NFS Version NFS Version 3
59 Number Procedures Procedures
61 0 null null
62 1 getattr getattr
63 2 setattr setattr
64 3 root lookup
65 4 lookup access
66 5 readlink readlink
67 6 read read
68 7 wrcache write
69 8 write create
70 9 create mkdir
71 10 remove symlink
72 11 rename mknod
73 12 link remove
74 13 symlink rmdir
75 14 mkdir rename
76 15 rmdir link
77 16 readdir readdir
78 17 fsstat readdirplus
79 18 fsstat
80 19 fsinfo
81 20 pathconf
82 21 commit
83 */
85 static const char *nfs2_procedures_names[] = {
86 "null", "getattr", "setattr", "root", "lookup", "readlink",
87 "read", "wrcache", "write", "create", "remove", "rename",
88 "link", "symlink", "mkdir", "rmdir", "readdir", "fsstat"};
89 static size_t nfs2_procedures_names_num =
90 STATIC_ARRAY_SIZE(nfs2_procedures_names);
92 static const char *nfs3_procedures_names[] = {
93 "null", "getattr", "setattr", "lookup", "access", "readlink",
94 "read", "write", "create", "mkdir", "symlink", "mknod",
95 "remove", "rmdir", "rename", "link", "readdir", "readdirplus",
96 "fsstat", "fsinfo", "pathconf", "commit"};
97 static size_t nfs3_procedures_names_num =
98 STATIC_ARRAY_SIZE(nfs3_procedures_names);
100 #if HAVE_LIBKSTAT
101 static const char *nfs4_procedures_names[] = {"null",
102 "compound",
103 "reserved",
104 "access",
105 "close",
106 "commit",
107 "create",
108 "delegpurge",
109 "delegreturn",
110 "getattr",
111 "getfh",
112 "link",
113 "lock",
114 "lockt",
115 "locku",
116 "lookup",
117 "lookupp",
118 "nverify",
119 "open",
120 "openattr",
121 "open_confirm",
122 "open_downgrade",
123 "putfh",
124 "putpubfh",
125 "putrootfh",
126 "read",
127 "readdir",
128 "readlink",
129 "remove",
130 "rename",
131 "renew",
132 "restorefh",
133 "savefh",
134 "secinfo",
135 "setattr",
136 "setclientid",
137 "setclientid_confirm",
138 "verify",
139 "write"};
140 static size_t nfs4_procedures_names_num =
141 STATIC_ARRAY_SIZE(nfs4_procedures_names);
142 #endif
144 #if KERNEL_LINUX
145 static const char *nfs4_server40_procedures_names[] = {"null",
146 "compound",
147 "reserved",
148 "access",
149 "close",
150 "commit",
151 "create",
152 "delegpurge",
153 "delegreturn",
154 "getattr",
155 "getfh",
156 "link",
157 "lock",
158 "lockt",
159 "locku",
160 "lookup",
161 "lookupp",
162 "nverify",
163 "open",
164 "openattr",
165 "open_confirm",
166 "open_downgrade",
167 "putfh",
168 "putpubfh",
169 "putrootfh",
170 "read",
171 "readdir",
172 "readlink",
173 "remove",
174 "rename",
175 "renew",
176 "restorefh",
177 "savefh",
178 "secinfo",
179 "setattr",
180 "setclientid",
181 "setcltid_confirm",
182 "verify",
183 "write",
184 "release_lockowner"};
186 static size_t nfs4_server40_procedures_names_num =
187 STATIC_ARRAY_SIZE(nfs4_server40_procedures_names);
189 static const char *nfs4_server41_procedures_names[] = {
190 "backchannel_ctl",
191 "bind_conn_to_session",
192 "exchange_id",
193 "create_session",
194 "destroy_session",
195 "free_stateid",
196 "get_dir_delegation",
197 "getdeviceinfo",
198 "getdevicelist",
199 "layoutcommit",
200 "layoutget",
201 "layoutreturn",
202 "secinfo_no_name",
203 "sequence",
204 "set_ssv",
205 "test_stateid",
206 "want_delegation",
207 "destroy_clientid",
208 "reclaim_complete",
209 };
211 static size_t nfs4_server41_procedures_names_num =
212 STATIC_ARRAY_SIZE(nfs4_server41_procedures_names);
214 #define NFS4_SERVER40_NUM_PROC \
215 (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names))
217 #define NFS4_SERVER41_NUM_PROC \
218 (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names) + \
219 STATIC_ARRAY_SIZE(nfs4_server41_procedures_names))
221 #define NFS4_SERVER_MAX_PROC (NFS4_SERVER41_NUM_PROC)
223 static const char *nfs4_client40_procedures_names[] = {
224 "null",
225 "read",
226 "write",
227 "commit",
228 "open",
229 "open_confirm",
230 "open_noattr",
231 "open_downgrade",
232 "close",
233 "setattr",
234 "fsinfo",
235 "renew",
236 "setclientid",
237 "setclientid_confirm",
238 "lock",
239 "lockt",
240 "locku",
241 "access",
242 "getattr",
243 "lookup",
244 "lookupp",
245 "remove",
246 "rename",
247 "link",
248 "symlink",
249 "create",
250 "pathconf",
251 "statfs",
252 "readlink",
253 "readdir",
254 "server_caps",
255 "delegreturn",
256 "getacl",
257 "setacl",
258 "fs_locations", /* |35| 2.6.18 */
259 "release_lockowner", /* |42| 2.6.36 */
260 "secinfo", /* |46| 2.6.39 */
261 "fsid_present" /* |54| 3.13 */
262 };
264 static const char *nfs4_client41_procedures_names[] = {
265 "exchange_id", /* |40| 2.6.30 */
266 "create_session", /* |40| 2.6.30 */
267 "destroy_session", /* |40| 2.6.30 */
268 "sequence", /* |40| 2.6.30 */
269 "get_lease_time", /* |40| 2.6.30 */
270 "reclaim_complete", /* |41| 2.6.33 */
271 "layoutget", /* |44| 2.6.37 */
272 "getdeviceinfo", /* |44| 2.6.37 */
273 "layoutcommit", /* |46| 2.6.39 */
274 "layoutreturn", /* |47| 3.0 */
275 "secinfo_no_name", /* |51| 3.1 */
276 "test_stateid", /* |51| 3.1 */
277 "free_stateid", /* |51| 3.1 */
278 "getdevicelist", /* |51| 3.1 */
279 "bind_conn_to_session", /* |53| 3.5 */
280 "destroy_clientid" /* |53| 3.5 */
281 };
283 #define NFS4_CLIENT40_NUM_PROC \
284 (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names))
286 #define NFS4_CLIENT41_NUM_PROC \
287 (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names) + \
288 STATIC_ARRAY_SIZE(nfs4_client41_procedures_names))
290 #define NFS4_CLIENT_MAX_PROC (NFS4_CLIENT41_NUM_PROC)
292 #endif
294 #if HAVE_LIBKSTAT
295 extern kstat_ctl_t *kc;
296 static kstat_t *nfs2_ksp_client;
297 static kstat_t *nfs2_ksp_server;
298 static kstat_t *nfs3_ksp_client;
299 static kstat_t *nfs3_ksp_server;
300 static kstat_t *nfs4_ksp_client;
301 static kstat_t *nfs4_ksp_server;
302 #endif
304 static int nfs_config(const char *key, const char *value) {
305 if (strcasecmp(key, "ReportV2") == 0)
306 report_v2 = IS_TRUE(value);
307 else if (strcasecmp(key, "ReportV3") == 0)
308 report_v3 = IS_TRUE(value);
309 else if (strcasecmp(key, "ReportV4") == 0)
310 report_v4 = IS_TRUE(value);
311 else
312 return -1;
314 return 0;
315 }
317 #if KERNEL_LINUX
318 static int nfs_init(void) { return 0; }
319 /* #endif KERNEL_LINUX */
321 #elif HAVE_LIBKSTAT
322 static int nfs_init(void) {
323 nfs2_ksp_client = NULL;
324 nfs2_ksp_server = NULL;
325 nfs3_ksp_client = NULL;
326 nfs3_ksp_server = NULL;
327 nfs4_ksp_client = NULL;
328 nfs4_ksp_server = NULL;
330 if (kc == NULL)
331 return -1;
333 for (kstat_t *ksp_chain = kc->kc_chain; ksp_chain != NULL;
334 ksp_chain = ksp_chain->ks_next) {
335 if (strncmp(ksp_chain->ks_module, "nfs", 3) != 0)
336 continue;
337 else if (strncmp(ksp_chain->ks_name, "rfsproccnt_v2", 13) == 0)
338 nfs2_ksp_server = ksp_chain;
339 else if (strncmp(ksp_chain->ks_name, "rfsproccnt_v3", 13) == 0)
340 nfs3_ksp_server = ksp_chain;
341 else if (strncmp(ksp_chain->ks_name, "rfsproccnt_v4", 13) == 0)
342 nfs4_ksp_server = ksp_chain;
343 else if (strncmp(ksp_chain->ks_name, "rfsreqcnt_v2", 12) == 0)
344 nfs2_ksp_client = ksp_chain;
345 else if (strncmp(ksp_chain->ks_name, "rfsreqcnt_v3", 12) == 0)
346 nfs3_ksp_client = ksp_chain;
347 else if (strncmp(ksp_chain->ks_name, "rfsreqcnt_v4", 12) == 0)
348 nfs4_ksp_client = ksp_chain;
349 }
351 return 0;
352 } /* int nfs_init */
353 #endif
355 static void nfs_procedures_submit(const char *plugin_instance,
356 const char **type_instances, value_t *values,
357 size_t values_num) {
358 value_list_t vl = VALUE_LIST_INIT;
360 vl.values_len = 1;
361 sstrncpy(vl.plugin, "nfs", sizeof(vl.plugin));
362 sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
363 sstrncpy(vl.type, "nfs_procedure", sizeof(vl.type));
365 for (size_t i = 0; i < values_num; i++) {
366 vl.values = values + i;
367 sstrncpy(vl.type_instance, type_instances[i], sizeof(vl.type_instance));
368 plugin_dispatch_values(&vl);
369 }
370 } /* void nfs_procedures_submit */
372 #if KERNEL_LINUX
373 static void nfs_submit_fields(int nfs_version, const char *instance,
374 char **fields, size_t fields_num,
375 const char **proc_names) {
376 char plugin_instance[DATA_MAX_NAME_LEN];
377 value_t values[fields_num];
379 snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
380 instance);
382 for (size_t i = 0; i < fields_num; i++)
383 (void)parse_value(fields[i], &values[i], DS_TYPE_DERIVE);
385 nfs_procedures_submit(plugin_instance, proc_names, values, fields_num);
386 }
388 static int nfs_submit_fields_safe(int nfs_version, const char *instance,
389 char **fields, size_t fields_num,
390 const char **proc_names,
391 size_t proc_names_num) {
392 if (fields_num != proc_names_num) {
393 WARNING("nfs plugin: Wrong number of fields for "
394 "NFSv%i %s statistics. Expected %zu, got %zu.",
395 nfs_version, instance, proc_names_num, fields_num);
396 return EINVAL;
397 }
399 nfs_submit_fields(nfs_version, instance, fields, fields_num, proc_names);
401 return 0;
402 }
404 static int nfs_submit_nfs4_server(const char *instance, char **fields,
405 size_t fields_num) {
406 static int suppress_warning = 0;
408 if (fields_num != NFS4_SERVER40_NUM_PROC &&
409 fields_num != NFS4_SERVER41_NUM_PROC) {
410 if (!suppress_warning) {
411 WARNING("nfs plugin: Unexpected number of fields for "
412 "NFSv4 %s statistics: %zu. ",
413 instance, fields_num);
414 }
416 if (fields_num > NFS4_SERVER_MAX_PROC) {
417 fields_num = NFS4_SERVER_MAX_PROC;
418 suppress_warning = 1;
419 } else {
420 return EINVAL;
421 }
422 }
424 nfs_submit_fields(4, instance, fields, nfs4_server40_procedures_names_num,
425 nfs4_server40_procedures_names);
427 if (fields_num >= NFS4_SERVER41_NUM_PROC) {
428 fields += nfs4_server40_procedures_names_num;
430 nfs_submit_fields(4, instance, fields, nfs4_server41_procedures_names_num,
431 nfs4_server41_procedures_names);
432 }
434 return 0;
435 }
437 static int nfs_submit_nfs4_client(const char *instance, char **fields,
438 size_t fields_num) {
439 size_t proc40_names_num, proc41_names_num;
441 static int suppress_warning = 0;
443 switch (fields_num) {
444 case 34:
445 case 35:
446 case 36:
447 case 37:
448 case 38:
449 /* 4.0-only configuration */
450 proc40_names_num = fields_num;
451 break;
452 case 40:
453 case 41:
454 proc40_names_num = 35;
455 break;
456 case 42:
457 case 44:
458 proc40_names_num = 36;
459 break;
460 case 46:
461 case 47:
462 case 51:
463 case 53:
464 proc40_names_num = 37;
465 break;
466 case 54:
467 proc40_names_num = 38;
468 break;
469 default:
470 if (!suppress_warning) {
471 WARNING("nfs plugin: Unexpected number of "
472 "fields for NFSv4 %s "
473 "statistics: %zu. ",
474 instance, fields_num);
475 }
477 if (fields_num > 34) {
478 /* safe fallback to basic nfs40 procedures */
479 fields_num = 34;
480 proc40_names_num = 34;
482 suppress_warning = 1;
483 } else {
484 return EINVAL;
485 }
486 }
488 nfs_submit_fields(4, instance, fields, proc40_names_num,
489 nfs4_client40_procedures_names);
491 if (fields_num > proc40_names_num) {
492 proc41_names_num = fields_num - proc40_names_num;
493 fields += proc40_names_num;
495 nfs_submit_fields(4, instance, fields, proc41_names_num,
496 nfs4_client41_procedures_names);
497 }
499 return 0;
500 }
502 static void nfs_read_linux(FILE *fh, const char *inst) {
503 char buffer[1024];
505 char *fields[64];
506 int fields_num = 0;
508 if (fh == NULL)
509 return;
511 while (fgets(buffer, sizeof(buffer), fh) != NULL) {
512 fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
514 if (fields_num < 3)
515 continue;
517 if (strcmp(fields[0], "proc2") == 0 && report_v2) {
518 nfs_submit_fields_safe(/* version = */ 2, inst, fields + 2,
519 (size_t)(fields_num - 2), nfs2_procedures_names,
520 nfs2_procedures_names_num);
521 } else if (strncmp(fields[0], "proc3", 5) == 0 && report_v3) {
522 nfs_submit_fields_safe(/* version = */ 3, inst, fields + 2,
523 (size_t)(fields_num - 2), nfs3_procedures_names,
524 nfs3_procedures_names_num);
525 } else if (strcmp(fields[0], "proc4ops") == 0 && report_v4) {
526 if (inst[0] == 's')
527 nfs_submit_nfs4_server(inst, fields + 2, (size_t)(fields_num - 2));
528 } else if (strcmp(fields[0], "proc4") == 0 && report_v4) {
529 if (inst[0] == 'c')
530 nfs_submit_nfs4_client(inst, fields + 2, (size_t)(fields_num - 2));
531 }
532 } /* while (fgets) */
533 } /* void nfs_read_linux */
534 #endif /* KERNEL_LINUX */
536 #if HAVE_LIBKSTAT
537 static int nfs_read_kstat(kstat_t *ksp, int nfs_version, const char *inst,
538 char const **proc_names, size_t proc_names_num) {
539 char plugin_instance[DATA_MAX_NAME_LEN];
540 value_t values[proc_names_num];
542 if (ksp == NULL)
543 return EINVAL;
545 snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
546 inst);
548 kstat_read(kc, ksp, NULL);
549 for (size_t i = 0; i < proc_names_num; i++) {
550 /* The name passed to kstat_data_lookup() doesn't have the
551 * "const" modifier, so we need to copy the name here. */
552 char name[32];
553 sstrncpy(name, proc_names[i], sizeof(name));
555 values[i].counter = (derive_t)get_kstat_value(ksp, name);
556 }
558 nfs_procedures_submit(plugin_instance, proc_names, values, proc_names_num);
559 return 0;
560 }
561 #endif
563 #if KERNEL_LINUX
564 static int nfs_read(void) {
565 FILE *fh;
567 if ((fh = fopen("/proc/net/rpc/nfs", "r")) != NULL) {
568 nfs_read_linux(fh, "client");
569 fclose(fh);
570 }
572 if ((fh = fopen("/proc/net/rpc/nfsd", "r")) != NULL) {
573 nfs_read_linux(fh, "server");
574 fclose(fh);
575 }
577 return 0;
578 }
579 /* #endif KERNEL_LINUX */
581 #elif HAVE_LIBKSTAT
582 static int nfs_read(void) {
583 if (report_v2) {
584 nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client",
585 nfs2_procedures_names, nfs2_procedures_names_num);
586 nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server",
587 nfs2_procedures_names, nfs2_procedures_names_num);
588 }
589 if (report_v3) {
590 nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client",
591 nfs3_procedures_names, nfs3_procedures_names_num);
592 nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server",
593 nfs3_procedures_names, nfs3_procedures_names_num);
594 }
595 if (report_v4) {
596 nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client",
597 nfs4_procedures_names, nfs4_procedures_names_num);
598 nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server",
599 nfs4_procedures_names, nfs4_procedures_names_num);
600 }
602 return 0;
603 }
604 #endif /* HAVE_LIBKSTAT */
606 void module_register(void) {
607 plugin_register_config("nfs", nfs_config, config_keys, config_keys_num);
608 plugin_register_init("nfs", nfs_init);
609 plugin_register_read("nfs", nfs_read);
610 } /* void module_register */