Merge pull request #2346 from trenkel/master
[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_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 };
210
211 static size_t nfs4_server41_procedures_names_num =
212     STATIC_ARRAY_SIZE(nfs4_server41_procedures_names);
213
214 #define NFS4_SERVER40_NUM_PROC                                                 \
215   (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names))
216
217 #define NFS4_SERVER41_NUM_PROC                                                 \
218   (STATIC_ARRAY_SIZE(nfs4_server40_procedures_names) +                         \
219    STATIC_ARRAY_SIZE(nfs4_server41_procedures_names))
220
221 #define NFS4_SERVER_MAX_PROC (NFS4_SERVER41_NUM_PROC)
222
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 };
263
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 };
282
283 #define NFS4_CLIENT40_NUM_PROC                                                 \
284   (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names))
285
286 #define NFS4_CLIENT41_NUM_PROC                                                 \
287   (STATIC_ARRAY_SIZE(nfs4_client40_procedures_names) +                         \
288    STATIC_ARRAY_SIZE(nfs4_client41_procedures_names))
289
290 #define NFS4_CLIENT_MAX_PROC (NFS4_CLIENT41_NUM_PROC)
291
292 #endif
293
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
303
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;
313
314   return 0;
315 }
316
317 #if KERNEL_LINUX
318 static int nfs_init(void) { return 0; }
319 /* #endif KERNEL_LINUX */
320
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;
329
330   if (kc == NULL)
331     return -1;
332
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   }
350
351   return 0;
352 } /* int nfs_init */
353 #endif
354
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;
359
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));
364
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 */
371
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];
378
379   snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
380            instance);
381
382   for (size_t i = 0; i < fields_num; i++)
383     (void)parse_value(fields[i], &values[i], DS_TYPE_DERIVE);
384
385   nfs_procedures_submit(plugin_instance, proc_names, values, fields_num);
386 }
387
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   }
398
399   nfs_submit_fields(nfs_version, instance, fields, fields_num, proc_names);
400
401   return 0;
402 }
403
404 static int nfs_submit_nfs4_server(const char *instance, char **fields,
405                                   size_t fields_num) {
406   static int suppress_warning = 0;
407
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     }
415
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   }
423
424   nfs_submit_fields(4, instance, fields, nfs4_server40_procedures_names_num,
425                     nfs4_server40_procedures_names);
426
427   if (fields_num >= NFS4_SERVER41_NUM_PROC) {
428     fields += nfs4_server40_procedures_names_num;
429
430     nfs_submit_fields(4, instance, fields, nfs4_server41_procedures_names_num,
431                       nfs4_server41_procedures_names);
432   }
433
434   return 0;
435 }
436
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;
440
441   static int suppress_warning = 0;
442
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     }
476
477     if (fields_num > 34) {
478       /* safe fallback to basic nfs40 procedures */
479       fields_num = 34;
480       proc40_names_num = 34;
481
482       suppress_warning = 1;
483     } else {
484       return EINVAL;
485     }
486   }
487
488   nfs_submit_fields(4, instance, fields, proc40_names_num,
489                     nfs4_client40_procedures_names);
490
491   if (fields_num > proc40_names_num) {
492     proc41_names_num = fields_num - proc40_names_num;
493     fields += proc40_names_num;
494
495     nfs_submit_fields(4, instance, fields, proc41_names_num,
496                       nfs4_client41_procedures_names);
497   }
498
499   return 0;
500 }
501
502 static void nfs_read_linux(FILE *fh, const char *inst) {
503   char buffer[1024];
504
505   char *fields[64];
506   int fields_num = 0;
507
508   if (fh == NULL)
509     return;
510
511   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
512     fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
513
514     if (fields_num < 3)
515       continue;
516
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 */
535
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];
541
542   if (ksp == NULL)
543     return EINVAL;
544
545   snprintf(plugin_instance, sizeof(plugin_instance), "v%i%s", nfs_version,
546            inst);
547
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));
554
555     values[i].counter = (derive_t)get_kstat_value(ksp, name);
556   }
557
558   nfs_procedures_submit(plugin_instance, proc_names, values, proc_names_num);
559   return 0;
560 }
561 #endif
562
563 #if KERNEL_LINUX
564 static int nfs_read(void) {
565   FILE *fh;
566
567   if ((fh = fopen("/proc/net/rpc/nfs", "r")) != NULL) {
568     nfs_read_linux(fh, "client");
569     fclose(fh);
570   }
571
572   if ((fh = fopen("/proc/net/rpc/nfsd", "r")) != NULL) {
573     nfs_read_linux(fh, "server");
574     fclose(fh);
575   }
576
577   return 0;
578 }
579 /* #endif KERNEL_LINUX */
580
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   }
601
602   return 0;
603 }
604 #endif /* HAVE_LIBKSTAT */
605
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 */