Merge remote-tracking branch 'origin/collectd-5.8'
[collectd.git] / src / nfs.c
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  **/
24
25 #include "collectd.h"
26
27 #include "common.h"
28 #include "plugin.h"
29
30 #if HAVE_KSTAT_H
31 #include <kstat.h>
32 #endif
33
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;
39
40 /*
41 see /proc/net/rpc/nfs
42 see http://www.missioncriticallinux.com/orph/NFS-Statistics
43
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.
49
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.
54
55 proc2 x x x...
56 proc3 x x x...
57
58 Procedure   NFS Version NFS Version 3
59 Number      Procedures  Procedures
60
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 */
84
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);
91
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);
99
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
143
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"};
185
186 static size_t nfs4_server40_procedures_names_num =
187     STATIC_ARRAY_SIZE(nfs4_server40_procedures_names);
188
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 };
211
212 #define NFS4_SERVER40_NUM_PROC                                                 \
213   (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names))
214
215 #define NFS4_SERVER4X_NUM_PROC                                                 \
216   (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names) +                         \
217    STATIC_ARRAY_SIZE(nfs4_server4x_procedures_names))
218
219 #define NFS4_SERVER_MAX_PROC (NFS4_SERVER4X_NUM_PROC)
220
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 };
261
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 };
288
289 #define NFS4_CLIENT40_NUM_PROC                                                 \
290   (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names))
291
292 #define NFS4_CLIENT4X_NUM_PROC                                                 \
293   (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names) +                         \
294    STATIC_ARRAY_SIZE(nfs4_client4x_procedures_names))
295
296 #define NFS4_CLIENT_MAX_PROC (NFS4_CLIENT4X_NUM_PROC)
297
298 #endif
299
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
309
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;
319
320   return 0;
321 }
322
323 #if KERNEL_LINUX
324 static int nfs_init(void) { return 0; }
325 /* #endif KERNEL_LINUX */
326
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;
335
336   if (kc == NULL)
337     return -1;
338
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   }
356
357   return 0;
358 } /* int nfs_init */
359 #endif
360
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;
365
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));
370
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 */
377
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];
384
385   snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
386            instance);
387
388   for (size_t i = 0; i < fields_num; i++)
389     (void)parse_value(fields[i], &values[i], DS_TYPE_DERIVE);
390
391   nfs_procedures_submit(plugin_instance, proc_names, values, fields_num);
392 }
393
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 %" PRIsz ", got %" PRIsz ".",
401             nfs_version, instance, proc_names_num, fields_num);
402     return EINVAL;
403   }
404
405   nfs_submit_fields(nfs_version, instance, fields, fields_num, proc_names);
406
407   return 0;
408 }
409
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;
414
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: %" PRIsz ". ",
425               instance, fields_num);
426     }
427
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   }
435
436   nfs_submit_fields(4, instance, fields, nfs4_server40_procedures_names_num,
437                     nfs4_server40_procedures_names);
438
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;
442
443     nfs_submit_fields(4, instance, fields, proc4x_names_num,
444                       nfs4_server4x_procedures_names);
445   }
446
447   return 0;
448 }
449
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;
453
454   static int suppress_warning = 0;
455
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 fields for NFSv4 %s "
490               "statistics: %" PRIsz ". ",
491               instance, fields_num);
492     }
493
494     if (fields_num > 34) {
495       /* safe fallback to basic nfs40 procedures */
496       fields_num = 34;
497       proc40_names_num = 34;
498
499       suppress_warning = 1;
500     } else {
501       return EINVAL;
502     }
503   }
504
505   nfs_submit_fields(4, instance, fields, proc40_names_num,
506                     nfs4_client40_procedures_names);
507
508   if (fields_num > proc40_names_num) {
509     proc4x_names_num = fields_num - proc40_names_num;
510     fields += proc40_names_num;
511
512     nfs_submit_fields(4, instance, fields, proc4x_names_num,
513                       nfs4_client4x_procedures_names);
514   }
515
516   return 0;
517 }
518
519 static void nfs_read_linux(FILE *fh, const char *inst) {
520   char buffer[1024];
521
522   char *fields[64];
523   int fields_num = 0;
524
525   if (fh == NULL)
526     return;
527
528   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
529     fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
530
531     if (fields_num < 3)
532       continue;
533
534     if (strcmp(fields[0], "proc2") == 0 && report_v2) {
535       nfs_submit_fields_safe(/* version = */ 2, inst, fields + 2,
536                              (size_t)(fields_num - 2), nfs2_procedures_names,
537                              nfs2_procedures_names_num);
538     } else if (strncmp(fields[0], "proc3", 5) == 0 && report_v3) {
539       nfs_submit_fields_safe(/* version = */ 3, inst, fields + 2,
540                              (size_t)(fields_num - 2), nfs3_procedures_names,
541                              nfs3_procedures_names_num);
542     } else if (strcmp(fields[0], "proc4ops") == 0 && report_v4) {
543       if (inst[0] == 's')
544         nfs_submit_nfs4_server(inst, fields + 2, (size_t)(fields_num - 2));
545     } else if (strcmp(fields[0], "proc4") == 0 && report_v4) {
546       if (inst[0] == 'c')
547         nfs_submit_nfs4_client(inst, fields + 2, (size_t)(fields_num - 2));
548     }
549   } /* while (fgets) */
550 } /* void nfs_read_linux */
551 #endif /* KERNEL_LINUX */
552
553 #if HAVE_LIBKSTAT
554 static int nfs_read_kstat(kstat_t *ksp, int nfs_version, const char *inst,
555                           char const **proc_names, size_t proc_names_num) {
556   char plugin_instance[DATA_MAX_NAME_LEN];
557   value_t values[proc_names_num];
558
559   if (ksp == NULL)
560     return EINVAL;
561
562   snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
563            inst);
564
565   kstat_read(kc, ksp, NULL);
566   for (size_t i = 0; i < proc_names_num; i++) {
567     /* The name passed to kstat_data_lookup() doesn't have the
568      * "const" modifier, so we need to copy the name here. */
569     char name[32];
570     sstrncpy(name, proc_names[i], sizeof(name));
571
572     values[i].counter = (derive_t)get_kstat_value(ksp, name);
573   }
574
575   nfs_procedures_submit(plugin_instance, proc_names, values, proc_names_num);
576   return 0;
577 }
578 #endif
579
580 #if KERNEL_LINUX
581 static int nfs_read(void) {
582   FILE *fh;
583
584   if ((fh = fopen("/proc/net/rpc/nfs", "r")) != NULL) {
585     nfs_read_linux(fh, "client");
586     fclose(fh);
587   }
588
589   if ((fh = fopen("/proc/net/rpc/nfsd", "r")) != NULL) {
590     nfs_read_linux(fh, "server");
591     fclose(fh);
592   }
593
594   return 0;
595 }
596 /* #endif KERNEL_LINUX */
597
598 #elif HAVE_LIBKSTAT
599 static int nfs_read(void) {
600   if (report_v2) {
601     nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client",
602                    nfs2_procedures_names, nfs2_procedures_names_num);
603     nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server",
604                    nfs2_procedures_names, nfs2_procedures_names_num);
605   }
606   if (report_v3) {
607     nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client",
608                    nfs3_procedures_names, nfs3_procedures_names_num);
609     nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server",
610                    nfs3_procedures_names, nfs3_procedures_names_num);
611   }
612   if (report_v4) {
613     nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client",
614                    nfs4_procedures_names, nfs4_procedures_names_num);
615     nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server",
616                    nfs4_procedures_names, nfs4_procedures_names_num);
617   }
618
619   return 0;
620 }
621 #endif /* HAVE_LIBKSTAT */
622
623 void module_register(void) {
624   plugin_register_config("nfs", nfs_config, config_keys, config_keys_num);
625   plugin_register_init("nfs", nfs_init);
626   plugin_register_read("nfs", nfs_read);
627 } /* void module_register */