Merge pull request #3329 from efuss/fix-3311
[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 "plugin.h"
28 #include "utils/common/common.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 = true;
37 static bool report_v3 = true;
38 static bool report_v4 = true;
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;
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;
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   // The stats line is prefixed with type and number of fields, thus plus 2
523   char *fields[MAX(NFS4_SERVER_MAX_PROC, NFS4_CLIENT_MAX_PROC) + 2];
524   int fields_num = 0;
525
526   if (fh == NULL)
527     return;
528
529   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
530     fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
531
532     if (fields_num < 3)
533       continue;
534
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 */
553
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];
559
560   if (ksp == NULL)
561     return EINVAL;
562
563   snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
564            inst);
565
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));
572
573     values[i].counter = (derive_t)get_kstat_value(ksp, name);
574   }
575
576   nfs_procedures_submit(plugin_instance, proc_names, values, proc_names_num);
577   return 0;
578 }
579 #endif
580
581 #if KERNEL_LINUX
582 static int nfs_read(void) {
583   FILE *fh;
584
585   if ((fh = fopen("/proc/net/rpc/nfs", "r")) != NULL) {
586     nfs_read_linux(fh, "client");
587     fclose(fh);
588   }
589
590   if ((fh = fopen("/proc/net/rpc/nfsd", "r")) != NULL) {
591     nfs_read_linux(fh, "server");
592     fclose(fh);
593   }
594
595   return 0;
596 }
597   /* #endif KERNEL_LINUX */
598
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   }
619
620   return 0;
621 }
622 #endif /* HAVE_LIBKSTAT */
623
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 */