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_server4x_procedures_names[] = {
190 /* NFS 4.1 */
191 "backchannel_ctl", "bind_conn_to_session", "exchange_id", "create_session",
192 "destroy_session", "free_stateid", "get_dir_delegation", "getdeviceinfo",
193 "getdevicelist", "layoutcommit", "layoutget", "layoutreturn",
194 "secinfo_no_name", "sequence", "set_ssv", "test_stateid", "want_delegation",
195 "destroy_clientid", "reclaim_complete",
196 /* NFS 4.2 */
197 "allocate", /* 3.18 */
198 "copy", /* 3.18 */
199 "copy_notify", /* 3.18 */
200 "deallocate", /* 3.18 */
201 "ioadvise", /* 3.18 */
202 "layouterror", /* 3.18 */
203 "layoutstats", /* 3.18 */
204 "offloadcancel", /* 3.18 */
205 "offloadstatus", /* 3.18 */
206 "readplus", /* 3.18 */
207 "seek", /* 3.18 */
208 "write_same", /* 3.18 */
209 "clone" /* 4.5 */
210 };
212 #define NFS4_SERVER40_NUM_PROC \
213 (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names))
215 #define NFS4_SERVER4X_NUM_PROC \
216 (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names) + \
217 STATIC_ARRAY_SIZE(nfs4_server4x_procedures_names))
219 #define NFS4_SERVER_MAX_PROC (NFS4_SERVER4X_NUM_PROC)
221 static const char *nfs4_client40_procedures_names[] = {
222 "null",
223 "read",
224 "write",
225 "commit",
226 "open",
227 "open_confirm",
228 "open_noattr",
229 "open_downgrade",
230 "close",
231 "setattr",
232 "fsinfo",
233 "renew",
234 "setclientid",
235 "setclientid_confirm",
236 "lock",
237 "lockt",
238 "locku",
239 "access",
240 "getattr",
241 "lookup",
242 "lookupp",
243 "remove",
244 "rename",
245 "link",
246 "symlink",
247 "create",
248 "pathconf",
249 "statfs",
250 "readlink",
251 "readdir",
252 "server_caps",
253 "delegreturn",
254 "getacl",
255 "setacl",
256 "fs_locations", /* |35| 2.6.18 */
257 "release_lockowner", /* |42| 2.6.36 */
258 "secinfo", /* |46| 2.6.39 */
259 "fsid_present" /* |54| 3.13 */
260 };
262 static const char *nfs4_client4x_procedures_names[] = {
263 /* NFS 4.1 */
264 "exchange_id", /* |40| 2.6.30 */
265 "create_session", /* |40| 2.6.30 */
266 "destroy_session", /* |40| 2.6.30 */
267 "sequence", /* |40| 2.6.30 */
268 "get_lease_time", /* |40| 2.6.30 */
269 "reclaim_complete", /* |41| 2.6.33 */
270 "layoutget", /* |44| 2.6.37 */
271 "getdeviceinfo", /* |44| 2.6.37 */
272 "layoutcommit", /* |46| 2.6.39 */
273 "layoutreturn", /* |47| 3.0 */
274 "secinfo_no_name", /* |51| 3.1 */
275 "test_stateid", /* |51| 3.1 */
276 "free_stateid", /* |51| 3.1 */
277 "getdevicelist", /* |51| 3.1 */
278 "bind_conn_to_session", /* |53| 3.5 */
279 "destroy_clientid", /* |53| 3.5 */
280 /* NFS 4.2 */
281 "seek", /* |55| 3.18 */
282 "allocate", /* |57| 3.19 */
283 "deallocate", /* |57| 3.19 */
284 "layoutstats", /* |58| 4.2 */
285 "clone", /* |59| 4.4 */
286 "copy" /* |60| 4.7 */
287 };
289 #define NFS4_CLIENT40_NUM_PROC \
290 (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names))
292 #define NFS4_CLIENT4X_NUM_PROC \
293 (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names) + \
294 STATIC_ARRAY_SIZE(nfs4_client4x_procedures_names))
296 #define NFS4_CLIENT_MAX_PROC (NFS4_CLIENT4X_NUM_PROC)
298 #endif
300 #if HAVE_LIBKSTAT
301 extern kstat_ctl_t *kc;
302 static kstat_t *nfs2_ksp_client;
303 static kstat_t *nfs2_ksp_server;
304 static kstat_t *nfs3_ksp_client;
305 static kstat_t *nfs3_ksp_server;
306 static kstat_t *nfs4_ksp_client;
307 static kstat_t *nfs4_ksp_server;
308 #endif
310 static int nfs_config(const char *key, const char *value) {
311 if (strcasecmp(key, "ReportV2") == 0)
312 report_v2 = IS_TRUE(value);
313 else if (strcasecmp(key, "ReportV3") == 0)
314 report_v3 = IS_TRUE(value);
315 else if (strcasecmp(key, "ReportV4") == 0)
316 report_v4 = IS_TRUE(value);
317 else
318 return -1;
320 return 0;
321 }
323 #if KERNEL_LINUX
324 static int nfs_init(void) { return 0; }
325 /* #endif KERNEL_LINUX */
327 #elif HAVE_LIBKSTAT
328 static int nfs_init(void) {
329 nfs2_ksp_client = NULL;
330 nfs2_ksp_server = NULL;
331 nfs3_ksp_client = NULL;
332 nfs3_ksp_server = NULL;
333 nfs4_ksp_client = NULL;
334 nfs4_ksp_server = NULL;
336 if (kc == NULL)
337 return -1;
339 for (kstat_t *ksp_chain = kc->kc_chain; ksp_chain != NULL;
340 ksp_chain = ksp_chain->ks_next) {
341 if (strncmp(ksp_chain->ks_module, "nfs", 3) != 0)
342 continue;
343 else if (strncmp(ksp_chain->ks_name, "rfsproccnt_v2", 13) == 0)
344 nfs2_ksp_server = ksp_chain;
345 else if (strncmp(ksp_chain->ks_name, "rfsproccnt_v3", 13) == 0)
346 nfs3_ksp_server = ksp_chain;
347 else if (strncmp(ksp_chain->ks_name, "rfsproccnt_v4", 13) == 0)
348 nfs4_ksp_server = ksp_chain;
349 else if (strncmp(ksp_chain->ks_name, "rfsreqcnt_v2", 12) == 0)
350 nfs2_ksp_client = ksp_chain;
351 else if (strncmp(ksp_chain->ks_name, "rfsreqcnt_v3", 12) == 0)
352 nfs3_ksp_client = ksp_chain;
353 else if (strncmp(ksp_chain->ks_name, "rfsreqcnt_v4", 12) == 0)
354 nfs4_ksp_client = ksp_chain;
355 }
357 return 0;
358 } /* int nfs_init */
359 #endif
361 static void nfs_procedures_submit(const char *plugin_instance,
362 const char **type_instances, value_t *values,
363 size_t values_num) {
364 value_list_t vl = VALUE_LIST_INIT;
366 vl.values_len = 1;
367 sstrncpy(vl.plugin, "nfs", sizeof(vl.plugin));
368 sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
369 sstrncpy(vl.type, "nfs_procedure", sizeof(vl.type));
371 for (size_t i = 0; i < values_num; i++) {
372 vl.values = values + i;
373 sstrncpy(vl.type_instance, type_instances[i], sizeof(vl.type_instance));
374 plugin_dispatch_values(&vl);
375 }
376 } /* void nfs_procedures_submit */
378 #if KERNEL_LINUX
379 static void nfs_submit_fields(int nfs_version, const char *instance,
380 char **fields, size_t fields_num,
381 const char **proc_names) {
382 char plugin_instance[DATA_MAX_NAME_LEN];
383 value_t values[fields_num];
385 snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
386 instance);
388 for (size_t i = 0; i < fields_num; i++)
389 (void)parse_value(fields[i], &values[i], DS_TYPE_DERIVE);
391 nfs_procedures_submit(plugin_instance, proc_names, values, fields_num);
392 }
394 static int nfs_submit_fields_safe(int nfs_version, const char *instance,
395 char **fields, size_t fields_num,
396 const char **proc_names,
397 size_t proc_names_num) {
398 if (fields_num != proc_names_num) {
399 WARNING("nfs plugin: Wrong number of fields for "
400 "NFSv%i %s statistics. Expected %zu, got %zu.",
401 nfs_version, instance, proc_names_num, fields_num);
402 return EINVAL;
403 }
405 nfs_submit_fields(nfs_version, instance, fields, fields_num, proc_names);
407 return 0;
408 }
410 static int nfs_submit_nfs4_server(const char *instance, char **fields,
411 size_t fields_num) {
412 static int suppress_warning = 0;
413 size_t proc4x_names_num;
415 switch (fields_num) {
416 case NFS4_SERVER40_NUM_PROC:
417 case NFS4_SERVER40_NUM_PROC + 19: /* NFS 4.1 */
418 case NFS4_SERVER40_NUM_PROC + 31: /* NFS 4.2 */
419 case NFS4_SERVER40_NUM_PROC + 32: /* NFS 4.2 */
420 break;
421 default:
422 if (!suppress_warning) {
423 WARNING("nfs plugin: Unexpected number of fields for "
424 "NFSv4 %s statistics: %zu. ",
425 instance, fields_num);
426 }
428 if (fields_num > NFS4_SERVER_MAX_PROC) {
429 fields_num = NFS4_SERVER_MAX_PROC;
430 suppress_warning = 1;
431 } else {
432 return EINVAL;
433 }
434 }
436 nfs_submit_fields(4, instance, fields, nfs4_server40_procedures_names_num,
437 nfs4_server40_procedures_names);
439 if (fields_num > nfs4_server40_procedures_names_num) {
440 proc4x_names_num = fields_num - nfs4_server40_procedures_names_num;
441 fields += nfs4_server40_procedures_names_num;
443 nfs_submit_fields(4, instance, fields, proc4x_names_num,
444 nfs4_server4x_procedures_names);
445 }
447 return 0;
448 }
450 static int nfs_submit_nfs4_client(const char *instance, char **fields,
451 size_t fields_num) {
452 size_t proc40_names_num, proc4x_names_num;
454 static int suppress_warning = 0;
456 switch (fields_num) {
457 case 34:
458 case 35:
459 case 36:
460 case 37:
461 case 38:
462 /* 4.0-only configuration */
463 proc40_names_num = fields_num;
464 break;
465 case 40:
466 case 41:
467 proc40_names_num = 35;
468 break;
469 case 42:
470 case 44:
471 proc40_names_num = 36;
472 break;
473 case 46:
474 case 47:
475 case 51:
476 case 53:
477 proc40_names_num = 37;
478 break;
479 case 54:
480 case 55:
481 case 57:
482 case 58:
483 case 59:
484 case 60:
485 proc40_names_num = 38;
486 break;
487 default:
488 if (!suppress_warning) {
489 WARNING("nfs plugin: Unexpected number of "
490 "fields for NFSv4 %s "
491 "statistics: %zu. ",
492 instance, fields_num);
493 }
495 if (fields_num > 34) {
496 /* safe fallback to basic nfs40 procedures */
497 fields_num = 34;
498 proc40_names_num = 34;
500 suppress_warning = 1;
501 } else {
502 return EINVAL;
503 }
504 }
506 nfs_submit_fields(4, instance, fields, proc40_names_num,
507 nfs4_client40_procedures_names);
509 if (fields_num > proc40_names_num) {
510 proc4x_names_num = fields_num - proc40_names_num;
511 fields += proc40_names_num;
513 nfs_submit_fields(4, instance, fields, proc4x_names_num,
514 nfs4_client4x_procedures_names);
515 }
517 return 0;
518 }
520 static void nfs_read_linux(FILE *fh, const char *inst) {
521 char buffer[1024];
523 char *fields[64];
524 int fields_num = 0;
526 if (fh == NULL)
527 return;
529 while (fgets(buffer, sizeof(buffer), fh) != NULL) {
530 fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
532 if (fields_num < 3)
533 continue;
535 if (strcmp(fields[0], "proc2") == 0 && report_v2) {
536 nfs_submit_fields_safe(/* version = */ 2, inst, fields + 2,
537 (size_t)(fields_num - 2), nfs2_procedures_names,
538 nfs2_procedures_names_num);
539 } else if (strncmp(fields[0], "proc3", 5) == 0 && report_v3) {
540 nfs_submit_fields_safe(/* version = */ 3, inst, fields + 2,
541 (size_t)(fields_num - 2), nfs3_procedures_names,
542 nfs3_procedures_names_num);
543 } else if (strcmp(fields[0], "proc4ops") == 0 && report_v4) {
544 if (inst[0] == 's')
545 nfs_submit_nfs4_server(inst, fields + 2, (size_t)(fields_num - 2));
546 } else if (strcmp(fields[0], "proc4") == 0 && report_v4) {
547 if (inst[0] == 'c')
548 nfs_submit_nfs4_client(inst, fields + 2, (size_t)(fields_num - 2));
549 }
550 } /* while (fgets) */
551 } /* void nfs_read_linux */
552 #endif /* KERNEL_LINUX */
554 #if HAVE_LIBKSTAT
555 static int nfs_read_kstat(kstat_t *ksp, int nfs_version, const char *inst,
556 char const **proc_names, size_t proc_names_num) {
557 char plugin_instance[DATA_MAX_NAME_LEN];
558 value_t values[proc_names_num];
560 if (ksp == NULL)
561 return EINVAL;
563 snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
564 inst);
566 kstat_read(kc, ksp, NULL);
567 for (size_t i = 0; i < proc_names_num; i++) {
568 /* The name passed to kstat_data_lookup() doesn't have the
569 * "const" modifier, so we need to copy the name here. */
570 char name[32];
571 sstrncpy(name, proc_names[i], sizeof(name));
573 values[i].counter = (derive_t)get_kstat_value(ksp, name);
574 }
576 nfs_procedures_submit(plugin_instance, proc_names, values, proc_names_num);
577 return 0;
578 }
579 #endif
581 #if KERNEL_LINUX
582 static int nfs_read(void) {
583 FILE *fh;
585 if ((fh = fopen("/proc/net/rpc/nfs", "r")) != NULL) {
586 nfs_read_linux(fh, "client");
587 fclose(fh);
588 }
590 if ((fh = fopen("/proc/net/rpc/nfsd", "r")) != NULL) {
591 nfs_read_linux(fh, "server");
592 fclose(fh);
593 }
595 return 0;
596 }
597 /* #endif KERNEL_LINUX */
599 #elif HAVE_LIBKSTAT
600 static int nfs_read(void) {
601 if (report_v2) {
602 nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client",
603 nfs2_procedures_names, nfs2_procedures_names_num);
604 nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server",
605 nfs2_procedures_names, nfs2_procedures_names_num);
606 }
607 if (report_v3) {
608 nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client",
609 nfs3_procedures_names, nfs3_procedures_names_num);
610 nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server",
611 nfs3_procedures_names, nfs3_procedures_names_num);
612 }
613 if (report_v4) {
614 nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client",
615 nfs4_procedures_names, nfs4_procedures_names_num);
616 nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server",
617 nfs4_procedures_names, nfs4_procedures_names_num);
618 }
620 return 0;
621 }
622 #endif /* HAVE_LIBKSTAT */
624 void module_register(void) {
625 plugin_register_config("nfs", nfs_config, config_keys, config_keys_num);
626 plugin_register_init("nfs", nfs_init);
627 plugin_register_read("nfs", nfs_read);
628 } /* void module_register */