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