Merge branch 'collectd-5.7' into 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 %zu, got %zu.",
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: %zu. ",
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 "
490               "fields for NFSv4 %s "
491               "statistics: %zu. ",
492               instance, fields_num);
493     }
494
495     if (fields_num > 34) {
496       /* safe fallback to basic nfs40 procedures */
497       fields_num = 34;
498       proc40_names_num = 34;
499
500       suppress_warning = 1;
501     } else {
502       return EINVAL;
503     }
504   }
505
506   nfs_submit_fields(4, instance, fields, proc40_names_num,
507                     nfs4_client40_procedures_names);
508
509   if (fields_num > proc40_names_num) {
510     proc4x_names_num = fields_num - proc40_names_num;
511     fields += proc40_names_num;
512
513     nfs_submit_fields(4, instance, fields, proc4x_names_num,
514                       nfs4_client4x_procedures_names);
515   }
516
517   return 0;
518 }
519
520 static void nfs_read_linux(FILE *fh, const char *inst) {
521   char buffer[1024];
522
523   // The stats line is prefixed with type and number of fields, thus plus 2
524   char *fields[MAX(NFS4_SERVER_MAX_PROC, NFS4_CLIENT_MAX_PROC) + 2];
525   int fields_num = 0;
526
527   if (fh == NULL)
528     return;
529
530   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
531     fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
532
533     if (fields_num < 3)
534       continue;
535
536     if (strcmp(fields[0], "proc2") == 0 && report_v2) {
537       nfs_submit_fields_safe(/* version = */ 2, inst, fields + 2,
538                              (size_t)(fields_num - 2), nfs2_procedures_names,
539                              nfs2_procedures_names_num);
540     } else if (strncmp(fields[0], "proc3", 5) == 0 && report_v3) {
541       nfs_submit_fields_safe(/* version = */ 3, inst, fields + 2,
542                              (size_t)(fields_num - 2), nfs3_procedures_names,
543                              nfs3_procedures_names_num);
544     } else if (strcmp(fields[0], "proc4ops") == 0 && report_v4) {
545       if (inst[0] == 's')
546         nfs_submit_nfs4_server(inst, fields + 2, (size_t)(fields_num - 2));
547     } else if (strcmp(fields[0], "proc4") == 0 && report_v4) {
548       if (inst[0] == 'c')
549         nfs_submit_nfs4_client(inst, fields + 2, (size_t)(fields_num - 2));
550     }
551   } /* while (fgets) */
552 } /* void nfs_read_linux */
553 #endif /* KERNEL_LINUX */
554
555 #if HAVE_LIBKSTAT
556 static int nfs_read_kstat(kstat_t *ksp, int nfs_version, const char *inst,
557                           char const **proc_names, size_t proc_names_num) {
558   char plugin_instance[DATA_MAX_NAME_LEN];
559   value_t values[proc_names_num];
560
561   if (ksp == NULL)
562     return EINVAL;
563
564   snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
565            inst);
566
567   kstat_read(kc, ksp, NULL);
568   for (size_t i = 0; i < proc_names_num; i++) {
569     /* The name passed to kstat_data_lookup() doesn't have the
570      * "const" modifier, so we need to copy the name here. */
571     char name[32];
572     sstrncpy(name, proc_names[i], sizeof(name));
573
574     values[i].counter = (derive_t)get_kstat_value(ksp, name);
575   }
576
577   nfs_procedures_submit(plugin_instance, proc_names, values, proc_names_num);
578   return 0;
579 }
580 #endif
581
582 #if KERNEL_LINUX
583 static int nfs_read(void) {
584   FILE *fh;
585
586   if ((fh = fopen("/proc/net/rpc/nfs", "r")) != NULL) {
587     nfs_read_linux(fh, "client");
588     fclose(fh);
589   }
590
591   if ((fh = fopen("/proc/net/rpc/nfsd", "r")) != NULL) {
592     nfs_read_linux(fh, "server");
593     fclose(fh);
594   }
595
596   return 0;
597 }
598 /* #endif KERNEL_LINUX */
599
600 #elif HAVE_LIBKSTAT
601 static int nfs_read(void) {
602   if (report_v2) {
603     nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client",
604                    nfs2_procedures_names, nfs2_procedures_names_num);
605     nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server",
606                    nfs2_procedures_names, nfs2_procedures_names_num);
607   }
608   if (report_v3) {
609     nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client",
610                    nfs3_procedures_names, nfs3_procedures_names_num);
611     nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server",
612                    nfs3_procedures_names, nfs3_procedures_names_num);
613   }
614   if (report_v4) {
615     nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client",
616                    nfs4_procedures_names, nfs4_procedures_names_num);
617     nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server",
618                    nfs4_procedures_names, nfs4_procedures_names_num);
619   }
620
621   return 0;
622 }
623 #endif /* HAVE_LIBKSTAT */
624
625 void module_register(void) {
626   plugin_register_config("nfs", nfs_config, config_keys, config_keys_num);
627   plugin_register_init("nfs", nfs_init);
628   plugin_register_read("nfs", nfs_read);
629 } /* void module_register */