Merge pull request #2512 from Stackdriver/pri
authorSebastian Harl <sh@tokkee.org>
Fri, 15 Dec 2017 13:10:08 +0000 (14:10 +0100)
committerGitHub <noreply@github.com>
Fri, 15 Dec 2017 13:10:08 +0000 (14:10 +0100)
Replace zu with PRIsz and llu with PRIu64.

16 files changed:
Makefile.am
configure.ac
contrib/systemd.collectd.service
src/collectd.conf.in
src/collectd.conf.pod
src/df.c
src/email.c
src/grpc.cc
src/liboconfig/parser.y
src/memcached.c
src/processes.c
src/turbostat.c
src/types.db
src/utils_tail_match.c
src/utils_taskstats.c [new file with mode: 0644]
src/utils_taskstats.h [new file with mode: 0644]

index ae027a3..e37716f 100644 (file)
@@ -516,6 +516,7 @@ liboconfig_la_SOURCES = \
        src/liboconfig/aux_types.h \
        src/liboconfig/scanner.l \
        src/liboconfig/parser.y
+liboconfig_la_CPPFLAGS = -I$(srcdir)/src/liboconfig $(AM_CPPFLAGS)
 liboconfig_la_LDFLAGS = -avoid-version $(LEXLIB)
 
 
@@ -1421,14 +1422,28 @@ python_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBPYTHON_CPPFLAGS)
 python_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBPYTHON_LDFLAGS)
 endif
 
+if HAVE_LIBMNL
+noinst_LTLIBRARIES += libtaskstats.la
+libtaskstats_la_SOURCES = \
+       src/utils_taskstats.c \
+       src/utils_taskstats.h
+libtaskstats_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBMNL_CFLAGS)
+libtaskstats_la_LIBADD = $(BUILD_WITH_LIBMNL_LIBS)
+endif
+
 if BUILD_PLUGIN_PROCESSES
 pkglib_LTLIBRARIES += processes.la
 processes_la_SOURCES = src/processes.c
+processes_la_CPPFLAGS = $(AM_CPPFLAGS)
 processes_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 processes_la_LIBADD =
 if BUILD_WITH_LIBKVM_GETPROCS
 processes_la_LIBADD += -lkvm
 endif
+if HAVE_LIBMNL
+processes_la_CPPFLAGS += -DHAVE_LIBTASKSTATS=1
+processes_la_LIBADD += libtaskstats.la
+endif
 endif
 
 if BUILD_PLUGIN_PROTOCOLS
index e869a6a..352a7b6 100644 (file)
@@ -2635,6 +2635,7 @@ AC_ARG_WITH([libgrpc++],
     if test "x$withval" != "xno" && test "x$withval" != "xyes"; then
       with_libgrpcpp_cppflags="-I$withval/include"
       with_libgrpcpp_ldflags="-L$withval/lib"
+      with_libgrpcpp_bin="$withval/bin"
       with_libgrpcpp="yes"
     fi
     if test "x$withval" = "xno"; then
@@ -2714,7 +2715,11 @@ AC_SUBST([BUILD_WITH_LIBGRPCPP_LIBS])
 # }}}
 
 AC_ARG_VAR([GRPC_CPP_PLUGIN], [path to the grpc_cpp_plugin binary])
-AC_PATH_PROG([GRPC_CPP_PLUGIN], [grpc_cpp_plugin])
+if test "x$with_libgrpcpp_bin" = "x"; then
+  AC_PATH_PROG([GRPC_CPP_PLUGIN], [grpc_cpp_plugin])
+else
+  AC_PATH_PROG([GRPC_CPP_PLUGIN], [grpc_cpp_plugin], [], "$with_libgrpcpp_bin:$PATH")
+fi
 AM_CONDITIONAL([HAVE_GRPC_CPP], [test "x$GRPC_CPP_PLUGIN" != "x"])
 
 # --with-libiptc {{{
@@ -3668,6 +3673,7 @@ if test "x$with_libmnl" = "xyes"; then
 fi
 AC_SUBST([BUILD_WITH_LIBMNL_CFLAGS])
 AC_SUBST([BUILD_WITH_LIBMNL_LIBS])
+AM_CONDITIONAL([HAVE_LIBMNL], [test "x$with_libmnl" = "xyes"])
 # }}}
 
 # --with-libnetapp {{{
@@ -6378,8 +6384,18 @@ if test "x$with_libgps" = "xyes"; then
   plugin_gps="yes"
 fi
 
-if test "x$with_libgrpcpp" = "xyes" && test "x$with_libprotobuf" = "xyes" && test "x$have_protoc3" = "xyes" && test "x$GRPC_CPP_PLUGIN" != "x"; then
-  plugin_grpc="yes"
+plugin_grpc="yes"
+if test "x$GRPC_CPP_PLUGIN" = "x"; then
+  plugin_grpc="no (grpc_cpp_plugin not found)"
+fi
+if test "x$have_protoc3" != "xyes"; then
+  plugin_grpc="no (protoc3 not found)"
+fi
+if test "x$with_libprotobuf" != "xyes"; then
+  plugin_grpc="no (libprotobuf not found)"
+fi
+if test "x$with_libgrpcpp" != "xyes"; then
+  plugin_grpc="no (libgrpc++ not found)"
 fi
 
 if test "x$have_getifaddrs" = "xyes"; then
index 9c037a4..c5b1142 100644 (file)
@@ -22,6 +22,7 @@ ProtectHome=true
 #   intel_pmu       CAP_SYS_ADMIN
 #   iptables        CAP_NET_ADMIN
 #   ping            CAP_NET_RAW
+#   processes       CAP_NET_ADMIN  (CollectDelayAccounting only)
 #   smart           CAP_SYS_RAWIO
 #   turbostat       CAP_SYS_RAWIO
 #
index 4efa29e..6ec61f3 100644 (file)
 #              SSLCACertificateFile "/path/to/root.pem"
 #              SSLCertificateFile "/path/to/client.pem"
 #              SSLCertificateKeyFile "/path/to/client.key"
+#              VerifyPeer true
 #      </Listen>
 #</Plugin>
 
 #      CollectFileDescriptor true
 #      CollectContextSwitch true
 #      CollectMemoryMaps true
+#      CollectDelayAccounting false
 #      Process "name"
 #      ProcessMatch "name" "regex"
 #      <Process "collectd">
 #              CollectFileDescriptor false
 #              CollectContextSwitch false
+#              CollectDelayAccounting true
 #      </Process>
 #      <ProcessMatch "name" "regex">
 #              CollectFileDescriptor false
index 0e7a604..dfd785a 100644 (file)
@@ -3094,6 +3094,13 @@ Whether to enable SSL for incoming connections. Default: false.
 Filenames specifying SSL certificate and key material to be used with SSL
 connections.
 
+=item B<VerifyPeer> B<true>|B<false>
+
+When enabled, a valid client certificate is required to connect to the server.
+When disabled, a client certifiacte is not requested and any unsolicited client
+certificate is accepted.
+Enabled by default.
+
 =back
 
 =back
@@ -6842,22 +6849,25 @@ The statistics collected for matched processes are:
  - number of memory mapped files (under Linux)
  - io data (where available)
  - context switches (under Linux)
- - minor and major pagefaults.
+ - minor and major pagefaults
+ - Delay Accounting information (Linux only, requires libmnl)
 
 B<Synopsis:>
 
  <Plugin processes>
-   CollectFileDescriptor true
-   CollectContextSwitch true
+   CollectFileDescriptor  true
+   CollectContextSwitch   true
+   CollectDelayAccounting false
    Process "name"
    ProcessMatch "name" "regex"
    <Process "collectd">
-     CollectFileDescriptor false
-     CollectContextSwitch false
+     CollectFileDescriptor  false
+     CollectContextSwitch   false
+     CollectDelayAccounting true
    </Process>
    <ProcessMatch "name" "regex">
      CollectFileDescriptor false
-     CollectContextSwitch true
+     CollectContextSwitch  true
    </Process>
  </Plugin>
 
@@ -6883,6 +6893,18 @@ I<name> must not contain slashes.
 Collect the number of context switches for matched processes.
 Disabled by default.
 
+=item B<CollectDelayAccounting> I<Boolean>
+
+If enabled, collect Linux Delay Accounding information for matching processes.
+Delay Accounting provides the time processes wait for the CPU to become
+available, for I/O operations to finish, for pages to be swapped in and for
+freed pages to be reclaimed. The metrics are reported as "seconds per second"
+using the C<delay_rate> type, e.g. C<delay_rate-delay-cpu>.
+Disabled by default.
+
+This option is only available on Linux, requires the C<libmnl> library and
+requires the C<CAP_NET_ADMIN> capability at runtime.
+
 =item B<CollectFileDescriptor> I<Boolean>
 
 Collect number of file descriptors of matched processes.
@@ -6896,9 +6918,12 @@ the Linux kernel.
 
 =back
 
-Options B<CollectContextSwitch> and B<CollectFileDescriptor> may be used inside
-B<Process> and B<ProcessMatch> blocks - then they affect corresponding match
-only. Otherwise they set the default value for subsequent matches.
+The B<CollectContextSwitch>, B<CollectDelayAccounting>,
+B<CollectFileDescriptor> and B<CollectMemoryMaps> options may be used inside
+B<Process> and B<ProcessMatch> blocks. When used there, these options affect
+reporting the corresponding processes only. Outside of B<Process> and
+B<ProcessMatch> blocks these options set the default value for subsequent
+matches.
 
 =head2 Plugin C<protocols>
 
index db8e519..e1d5b51 100644 (file)
--- a/src/df.c
+++ b/src/df.c
@@ -152,6 +152,7 @@ static int df_read(void) {
 #elif HAVE_STATFS
   struct statfs statbuf;
 #endif
+  int retval = 0;
   /* struct STATANYFS statbuf; */
   cu_mount_t *mnt_list;
 
@@ -282,8 +283,10 @@ static int df_read(void) {
             (gauge_t)((float_t)(blk_reserved) / statbuf.f_blocks * 100));
         df_submit_one(disk_name, "percent_bytes", "used",
                       (gauge_t)((float_t)(blk_used) / statbuf.f_blocks * 100));
-      } else
-        return -1;
+      } else {
+        retval = -1;
+        break;
+      }
     }
 
     /* inode handling */
@@ -313,8 +316,10 @@ static int df_read(void) {
           df_submit_one(
               disk_name, "percent_inodes", "used",
               (gauge_t)((float_t)(inode_used) / statbuf.f_files * 100));
-        } else
-          return -1;
+        } else {
+          retval = -1;
+          break;
+        }
       }
       if (values_absolute) {
         df_submit_one(disk_name, "df_inodes", "free", (gauge_t)inode_free);
@@ -327,7 +332,7 @@ static int df_read(void) {
 
   cu_mount_freelist(mnt_list);
 
-  return 0;
+  return retval;
 } /* int df_read */
 
 void module_register(void) {
index 91764d2..d94c542 100644 (file)
@@ -297,22 +297,21 @@ static void *collect(void *arg) {
       }
 
       if (line[0] == 'e') { /* e:<type>:<bytes> */
-        char *ptr = NULL;
-        char *type = strtok_r(line + 2, ":", &ptr);
-        char *tmp = strtok_r(NULL, ":", &ptr);
-        int bytes = 0;
-
-        if (tmp == NULL) {
+        char *type = line + 2;
+        char *bytes_str = strchr(type, ':');
+        if (bytes_str == NULL) {
           log_err("collect: syntax error in line '%s'", line);
           continue;
         }
 
-        bytes = atoi(tmp);
+        *bytes_str = 0;
+        bytes_str++;
 
         pthread_mutex_lock(&count_mutex);
         type_list_incr(&list_count, type, /* increment = */ 1);
         pthread_mutex_unlock(&count_mutex);
 
+        int bytes = atoi(bytes_str);
         if (bytes > 0) {
           pthread_mutex_lock(&size_mutex);
           type_list_incr(&list_size, type, /* increment = */ bytes);
@@ -370,7 +369,9 @@ static void *open_connection(void __attribute__((unused)) * arg) {
     pthread_exit((void *)1);
   }
 
-  struct sockaddr_un addr = {.sun_family = AF_UNIX};
+  struct sockaddr_un addr = {
+      .sun_family = AF_UNIX,
+  };
   sstrncpy(addr.sun_path, path, (size_t)(UNIX_PATH_MAX - 1));
 
   errno = 0;
index 0f5cfec..314e9b0 100644 (file)
@@ -56,7 +56,8 @@ using collectd::QueryValuesResponse;
 
 using google::protobuf::util::TimeUtil;
 
-typedef google::protobuf::Map<grpc::string, collectd::types::MetadataValue> grpcMetadata;
+typedef google::protobuf::Map<grpc::string, collectd::types::MetadataValue>
+    grpcMetadata;
 
 /*
  * private types
@@ -175,10 +176,11 @@ static grpc::Status marshal_meta_data(meta_data_t *meta,
     switch (md_type) {
     case MD_TYPE_STRING:
       char *md_string;
-      if (meta_data_get_string(meta, key, &md_string) != 0 || md_string == nullptr) {
+      if (meta_data_get_string(meta, key, &md_string) != 0 ||
+          md_string == nullptr) {
         strarray_free(meta_data_keys, meta_data_keys_len);
         return grpc::Status(grpc::StatusCode::INTERNAL,
-                          grpc::string("missing metadata"));
+                            grpc::string("missing metadata"));
       }
       md_value.set_string_value(md_string);
       free(md_string);
@@ -188,7 +190,7 @@ static grpc::Status marshal_meta_data(meta_data_t *meta,
       if (meta_data_get_signed_int(meta, key, &int64_value) != 0) {
         strarray_free(meta_data_keys, meta_data_keys_len);
         return grpc::Status(grpc::StatusCode::INTERNAL,
-                          grpc::string("missing metadata"));
+                            grpc::string("missing metadata"));
       }
       md_value.set_int64_value(int64_value);
       break;
@@ -197,7 +199,7 @@ static grpc::Status marshal_meta_data(meta_data_t *meta,
       if (meta_data_get_unsigned_int(meta, key, &uint64_value) != 0) {
         strarray_free(meta_data_keys, meta_data_keys_len);
         return grpc::Status(grpc::StatusCode::INTERNAL,
-                          grpc::string("missing metadata"));
+                            grpc::string("missing metadata"));
       }
       md_value.set_uint64_value(uint64_value);
       break;
@@ -206,7 +208,7 @@ static grpc::Status marshal_meta_data(meta_data_t *meta,
       if (meta_data_get_double(meta, key, &double_value) != 0) {
         strarray_free(meta_data_keys, meta_data_keys_len);
         return grpc::Status(grpc::StatusCode::INTERNAL,
-                          grpc::string("missing metadata"));
+                            grpc::string("missing metadata"));
       }
       md_value.set_double_value(double_value);
       break;
@@ -215,7 +217,7 @@ static grpc::Status marshal_meta_data(meta_data_t *meta,
       if (meta_data_get_boolean(meta, key, &bool_value) != 0) {
         strarray_free(meta_data_keys, meta_data_keys_len);
         return grpc::Status(grpc::StatusCode::INTERNAL,
-                          grpc::string("missing metadata"));
+                            grpc::string("missing metadata"));
       }
       md_value.set_bool_value(bool_value);
       break;
@@ -241,7 +243,7 @@ static grpc::Status unmarshal_meta_data(const grpcMetadata &rpc_metadata,
     return grpc::Status(grpc::StatusCode::RESOURCE_EXHAUSTED,
                         grpc::string("failed to metadata list"));
   }
-  for (auto kv: rpc_metadata) {
+  for (auto kv : rpc_metadata) {
     auto k = kv.first.c_str();
     auto v = kv.second;
 
@@ -267,8 +269,8 @@ static grpc::Status unmarshal_meta_data(const grpcMetadata &rpc_metadata,
       break;
     default:
       meta_data_destroy(*md_out);
-      return  grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
-                           grpc::string("Metadata of unknown type"));
+      return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+                          grpc::string("Metadata of unknown type"));
     }
   }
   return grpc::Status::OK;
@@ -482,8 +484,9 @@ private:
         break;
       }
       if (uc_iterator_get_meta(iter, &vl.meta) < 0) {
-        status = grpc::Status(grpc::StatusCode::INTERNAL,
-                              grpc::string("failed to retrieve value metadata"));
+        status =
+            grpc::Status(grpc::StatusCode::INTERNAL,
+                         grpc::string("failed to retrieve value metadata"));
       }
 
       value_lists->push(vl);
@@ -626,7 +629,8 @@ static int c_grpc_config_listen(oconfig_item_t *ci) {
   listener.port = grpc::string(ci->values[1].value.string);
   listener.ssl = nullptr;
 
-  auto ssl_opts = new (grpc::SslServerCredentialsOptions);
+  auto ssl_opts = new grpc::SslServerCredentialsOptions(
+      GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
   grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp = {};
   bool use_ssl = false;
 
@@ -659,6 +663,14 @@ static int c_grpc_config_listen(oconfig_item_t *ci) {
         return -1;
       }
       pkcp.cert_chain = read_file(cert);
+    } else if (!strcasecmp("VerifyPeer", child->key)) {
+      _Bool verify = 0;
+      if (cf_util_get_boolean(child, &verify)) {
+        return -1;
+      }
+      ssl_opts->client_certificate_request =
+          verify ? GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
+                 : GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE;
     } else {
       WARNING("grpc: Option `%s` not allowed in <%s> block.", child->key,
               ci->key);
index 4a550b3..90f51de 100644 (file)
@@ -31,7 +31,7 @@
 #include "aux_types.h"
 
 static char *unquote (const char *orig);
-static int yyerror (const char *s);
+static void yyerror(const char *s);
 
 /* Lexer variables */
 extern int yylineno;
@@ -94,13 +94,23 @@ argument_list:
        argument_list argument
        {
         $$ = $1;
+        oconfig_value_t *tmp = realloc($$.argument,
+                                       ($$.argument_num+1) * sizeof(*$$.argument));
+        if (tmp == NULL) {
+          yyerror("realloc failed");
+          YYERROR;
+        }
+        $$.argument = tmp;
+        $$.argument[$$.argument_num] = $2;
         $$.argument_num++;
-        $$.argument = realloc ($$.argument, $$.argument_num * sizeof (oconfig_value_t));
-        $$.argument[$$.argument_num-1] = $2;
        }
        | argument
        {
-        $$.argument = malloc (sizeof (oconfig_value_t));
+        $$.argument = calloc(1, sizeof(*$$.argument));
+        if ($$.argument == NULL) {
+          yyerror("calloc failed");
+          YYERROR;
+        }
         $$.argument[0] = $1;
         $$.argument_num = 1;
        }
@@ -113,7 +123,7 @@ identifier:
 option:
        identifier argument_list EOL
        {
-        memset (&$$, '\0', sizeof ($$));
+        memset(&$$, 0, sizeof($$));
         $$.key = $1;
         $$.values = $2.argument;
         $$.values_num = $2.argument_num;
@@ -123,13 +133,13 @@ option:
 block_begin:
        OPENBRAC identifier CLOSEBRAC EOL
        {
-        memset (&$$, '\0', sizeof ($$));
+        memset(&$$, 0, sizeof($$));
         $$.key = $2;
        }
        |
        OPENBRAC identifier argument_list CLOSEBRAC EOL
        {
-        memset (&$$, '\0', sizeof ($$));
+        memset(&$$, 0, sizeof($$));
         $$.key = $2;
         $$.values = $3.argument;
         $$.values_num = $3.argument_num;
@@ -146,11 +156,11 @@ block_end:
 block:
        block_begin statement_list block_end
        {
-        if (strcmp ($1.key, $3) != 0)
+        if (strcmp($1.key, $3) != 0)
         {
-               printf ("block_begin = %s; block_end = %s;\n", $1.key, $3);
-               yyerror ("Block not closed..\n");
-               exit (1);
+               printf("block_begin = %s; block_end = %s;\n", $1.key, $3);
+               yyerror("block not closed");
+               YYERROR;
         }
         free ($3); $3 = NULL;
         $$ = $1;
@@ -159,11 +169,11 @@ block:
        }
        | block_begin block_end
        {
-        if (strcmp ($1.key, $2) != 0)
+        if (strcmp($1.key, $2) != 0)
         {
-               printf ("block_begin = %s; block_end = %s;\n", $1.key, $2);
-               yyerror ("Block not closed..\n");
-               exit (1);
+               printf("block_begin = %s; block_end = %s;\n", $1.key, $2);
+               yyerror("block not closed");
+               YYERROR;
         }
         free ($2); $2 = NULL;
         $$ = $1;
@@ -184,16 +194,26 @@ statement_list:
         $$ = $1;
         if (($2.values_num > 0) || ($2.children_num > 0))
         {
+                oconfig_item_t *tmp = realloc($$.statement,
+                                              ($$.statement_num+1) * sizeof(*tmp));
+                if (tmp == NULL) {
+                  yyerror("realloc failed");
+                  YYERROR;
+                }
+                $$.statement = tmp;
+                $$.statement[$$.statement_num] = $2;
                 $$.statement_num++;
-                $$.statement = realloc ($$.statement, $$.statement_num * sizeof (oconfig_item_t));
-                $$.statement[$$.statement_num-1] = $2;
         }
        }
        | statement
        {
         if (($1.values_num > 0) || ($1.children_num > 0))
         {
-                $$.statement = malloc (sizeof (oconfig_item_t));
+                $$.statement = calloc(1, sizeof(*$$.statement));
+                if ($$.statement == NULL) {
+                  yyerror("calloc failed");
+                  YYERROR;
+                }
                 $$.statement[0] = $1;
                 $$.statement_num = 1;
         }
@@ -208,31 +228,38 @@ statement_list:
 entire_file:
        statement_list
        {
-        ci_root = calloc (1, sizeof (*ci_root));
+        ci_root = calloc(1, sizeof(*ci_root));
+        if (ci_root == NULL) {
+          yyerror("calloc failed");
+          YYERROR;
+        }
         ci_root->children = $1.statement;
         ci_root->children_num = $1.statement_num;
        }
        | /* epsilon */
        {
-        ci_root = calloc (1, sizeof (*ci_root));
-        ci_root->children = NULL;
-        ci_root->children_num = 0;
+        ci_root = calloc(1, sizeof(*ci_root));
+        if (ci_root == NULL) {
+          yyerror("calloc failed");
+          YYERROR;
+        }
        }
        ;
 
 %%
-static int yyerror (const char *s)
+static void yyerror(const char *s)
 {
        const char *text;
 
-       if (*yytext == '\n')
+       if (yytext == NULL)
+               text = "<empty>";
+       else if (*yytext == '\n')
                text = "<newline>";
        else
                text = yytext;
 
-       fprintf (stderr, "Parse error in file `%s', line %i near `%s': %s\n",
+       fprintf(stderr, "Parse error in file `%s', line %i near `%s': %s\n",
                c_file, yylineno, text, s);
-       return (-1);
 } /* int yyerror */
 
 static char *unquote (const char *orig)
@@ -250,7 +277,7 @@ static char *unquote (const char *orig)
 
        len -= 2;
        memmove (ret, ret + 1, len);
-       ret[len] = '\0';
+       ret[len] = 0;
 
        for (int i = 0; i < len; i++)
        {
index d62b25d..635088e 100644 (file)
@@ -285,6 +285,12 @@ static int memcached_query_daemon(char *buffer, size_t buffer_size,
       close(st->fd);
       st->fd = -1;
       return -1;
+    } else if (status == 0) {
+      ERROR("memcached plugin: Instance \"%s\": Connection closed by peer",
+            st->name);
+      close(st->fd);
+      st->fd = -1;
+      return -1;
     }
 
     buffer_fill += (size_t)status;
index a328730..d73d24a 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * collectd - src/processes.c
  * Copyright (C) 2005       Lyonel Vincent
- * Copyright (C) 2006-2010  Florian octo Forster
+ * Copyright (C) 2006-2017  Florian octo Forster
  * Copyright (C) 2008       Oleg King
  * Copyright (C) 2009       Sebastian Harl
  * Copyright (C) 2009       Andrés J. Díaz
 #include "common.h"
 #include "plugin.h"
 
+#if HAVE_LIBTASKSTATS
+#include "utils_complain.h"
+#include "utils_taskstats.h"
+#endif
+
 /* Include header files for the mach system, if they exist.. */
 #if HAVE_THREAD_INFO
 #if HAVE_MACH_MACH_INIT_H
 #include <kstat.h>
 #endif
 
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+#endif
+
 #ifndef CMDLINE_BUFFER_SIZE
 #if defined(ARG_MAX) && (ARG_MAX < 4096)
 #define CMDLINE_BUFFER_SIZE ARG_MAX
@@ -195,6 +204,11 @@ typedef struct process_entry_s {
   derive_t cswitch_invol;
   _Bool has_cswitch;
 
+#if HAVE_LIBTASKSTATS
+  ts_delay_t delay;
+#endif
+  _Bool has_delay;
+
   _Bool has_fd;
 
   _Bool has_maps;
@@ -221,6 +235,13 @@ typedef struct procstat_entry_s {
   derive_t cswitch_vol;
   derive_t cswitch_invol;
 
+#if HAVE_LIBTASKSTATS
+  value_to_rate_state_t delay_cpu;
+  value_to_rate_state_t delay_blkio;
+  value_to_rate_state_t delay_swapin;
+  value_to_rate_state_t delay_freepages;
+#endif
+
   struct procstat_entry_s *next;
 } procstat_entry_t;
 
@@ -257,9 +278,16 @@ typedef struct procstat {
   derive_t cswitch_vol;
   derive_t cswitch_invol;
 
+  /* Linux Delay Accounting. Unit is ns/s. */
+  gauge_t delay_cpu;
+  gauge_t delay_blkio;
+  gauge_t delay_swapin;
+  gauge_t delay_freepages;
+
   _Bool report_fd_num;
   _Bool report_maps_num;
   _Bool report_ctx_switch;
+  _Bool report_delay;
 
   struct procstat *next;
   struct procstat_entry_s *instances;
@@ -271,6 +299,7 @@ static _Bool want_init = 1;
 static _Bool report_ctx_switch = 0;
 static _Bool report_fd_num = 0;
 static _Bool report_maps_num = 0;
+static _Bool report_delay = 0;
 
 #if HAVE_THREAD_INFO
 static mach_port_t port_host_self;
@@ -304,6 +333,10 @@ int getthrds64(pid_t, void *, int, tid64_t *, int);
 int getargs(void *processBuffer, int bufferLen, char *argsBuffer, int argsLen);
 #endif /* HAVE_PROCINFO_H */
 
+#if HAVE_LIBTASKSTATS
+static ts_t *taskstats_handle = NULL;
+#endif
+
 /* put name of process from config to list_head_g tree
  * list_head_g is a list of 'procstat_t' structs with
  * processes names we want to watch */
@@ -331,6 +364,7 @@ static procstat_t *ps_list_register(const char *name, const char *regexp) {
   new->report_fd_num = report_fd_num;
   new->report_maps_num = report_maps_num;
   new->report_ctx_switch = report_ctx_switch;
+  new->report_delay = report_delay;
 
 #if HAVE_REGEX_H
   if (regexp != NULL) {
@@ -439,6 +473,39 @@ static void ps_update_counter(derive_t *group_counter, derive_t *curr_counter,
   *group_counter += curr_value;
 }
 
+#if HAVE_LIBTASKSTATS
+static void ps_update_delay_one(gauge_t *out_rate_sum,
+                                value_to_rate_state_t *state, uint64_t cnt,
+                                cdtime_t t) {
+  gauge_t rate = NAN;
+  int status = value_to_rate(&rate, (value_t){.counter = (counter_t)cnt},
+                             DS_TYPE_COUNTER, t, state);
+  if ((status != 0) || isnan(rate)) {
+    return;
+  }
+
+  if (isnan(*out_rate_sum)) {
+    *out_rate_sum = rate;
+  } else {
+    *out_rate_sum += rate;
+  }
+}
+
+static void ps_update_delay(procstat_t *out, procstat_entry_t *prev,
+                            process_entry_t *curr) {
+  cdtime_t now = cdtime();
+
+  ps_update_delay_one(&out->delay_cpu, &prev->delay_cpu, curr->delay.cpu_ns,
+                      now);
+  ps_update_delay_one(&out->delay_blkio, &prev->delay_blkio,
+                      curr->delay.blkio_ns, now);
+  ps_update_delay_one(&out->delay_swapin, &prev->delay_swapin,
+                      curr->delay.swapin_ns, now);
+  ps_update_delay_one(&out->delay_freepages, &prev->delay_freepages,
+                      curr->delay.freepages_ns, now);
+}
+#endif
+
 /* add process entry to 'instances' of process 'name' (or refresh it) */
 static void ps_list_add(const char *name, const char *cmdline,
                         process_entry_t *entry) {
@@ -518,6 +585,10 @@ static void ps_list_add(const char *name, const char *cmdline,
                       entry->cpu_user_counter);
     ps_update_counter(&ps->cpu_system_counter, &pse->cpu_system_counter,
                       entry->cpu_system_counter);
+
+#if HAVE_LIBTASKSTATS
+    ps_update_delay(ps, pse, entry);
+#endif
   }
 }
 
@@ -537,6 +608,11 @@ static void ps_list_reset(void) {
     ps->vmem_code = 0;
     ps->stack_size = 0;
 
+    ps->delay_cpu = NAN;
+    ps->delay_blkio = NAN;
+    ps->delay_swapin = NAN;
+    ps->delay_freepages = NAN;
+
     pse_prev = NULL;
     pse = ps->instances;
     while (pse != NULL) {
@@ -573,8 +649,15 @@ static void ps_tune_instance(oconfig_item_t *ci, procstat_t *ps) {
       cf_util_get_boolean(c, &ps->report_fd_num);
     else if (strcasecmp(c->key, "CollectMemoryMaps") == 0)
       cf_util_get_boolean(c, &ps->report_maps_num);
-    else {
-      ERROR("processes plugin: Option `%s' not allowed here.", c->key);
+    else if (strcasecmp(c->key, "CollectDelayAccounting") == 0) {
+#if HAVE_LIBTASKSTATS
+      cf_util_get_boolean(c, &ps->report_delay);
+#else
+      WARNING("processes plugin: The plugin has been compiled without support "
+              "for the \"CollectDelayAccounting\" option.");
+#endif
+    } else {
+      ERROR("processes plugin: Option \"%s\" not allowed here.", c->key);
     }
   } /* for (ci->children) */
 } /* void ps_tune_instance */
@@ -634,6 +717,13 @@ static int ps_config(oconfig_item_t *ci) {
       cf_util_get_boolean(c, &report_fd_num);
     } else if (strcasecmp(c->key, "CollectMemoryMaps") == 0) {
       cf_util_get_boolean(c, &report_maps_num);
+    } else if (strcasecmp(c->key, "CollectDelayAccounting") == 0) {
+#if HAVE_LIBTASKSTATS
+      cf_util_get_boolean(c, &report_delay);
+#else
+      WARNING("processes plugin: The plugin has been compiled without support "
+              "for the \"CollectDelayAccounting\" option.");
+#endif
     } else {
       ERROR("processes plugin: The `%s' configuration option is not "
             "understood and will be ignored.",
@@ -671,6 +761,15 @@ static int ps_init(void) {
 #elif KERNEL_LINUX
   pagesize_g = sysconf(_SC_PAGESIZE);
   DEBUG("pagesize_g = %li; CONFIG_HZ = %i;", pagesize_g, CONFIG_HZ);
+
+#if HAVE_LIBTASKSTATS
+  if (taskstats_handle == NULL) {
+    taskstats_handle = ts_create();
+    if (taskstats_handle == NULL) {
+      WARNING("processes plugin: Creating taskstats handle failed.");
+    }
+  }
+#endif
 /* #endif KERNEL_LINUX */
 
 #elif HAVE_LIBKVM_GETPROCS &&                                                  \
@@ -805,6 +904,31 @@ static void ps_submit_proc_list(procstat_t *ps) {
     plugin_dispatch_values(&vl);
   }
 
+  /* The ps->delay_* metrics are in nanoseconds per second. Convert to seconds
+   * per second. */
+  gauge_t const delay_factor = 1000000000.0;
+
+  struct {
+    char *type_instance;
+    gauge_t rate_ns;
+  } delay_metrics[] = {
+      {"delay-cpu", ps->delay_cpu},
+      {"delay-blkio", ps->delay_blkio},
+      {"delay-swapin", ps->delay_swapin},
+      {"delay-freepages", ps->delay_freepages},
+  };
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(delay_metrics); i++) {
+    if (isnan(delay_metrics[i].rate_ns)) {
+      continue;
+    }
+    sstrncpy(vl.type, "delay_rate", sizeof(vl.type));
+    sstrncpy(vl.type_instance, delay_metrics[i].type_instance,
+             sizeof(vl.type_instance));
+    vl.values[0].gauge = delay_metrics[i].rate_ns * delay_factor;
+    vl.values_len = 1;
+    plugin_dispatch_values(&vl);
+  }
+
   DEBUG(
       "name = %s; num_proc = %lu; num_lwp = %lu; num_fd = %lu; num_maps = %lu; "
       "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; "
@@ -814,13 +938,16 @@ static void ps_submit_proc_list(procstat_t *ps) {
       "io_rchar = %" PRIi64 "; io_wchar = %" PRIi64 "; "
       "io_syscr = %" PRIi64 "; io_syscw = %" PRIi64 "; "
       "io_diskr = %" PRIi64 "; io_diskw = %" PRIi64 "; "
-      "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 ";",
+      "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 "; "
+      "delay_cpu = %g; delay_blkio = %g; "
+      "delay_swapin = %g; delay_freepages = %g;",
       ps->name, ps->num_proc, ps->num_lwp, ps->num_fd, ps->num_maps,
       ps->vmem_size, ps->vmem_rss, ps->vmem_data, ps->vmem_code,
       ps->vmem_minflt_counter, ps->vmem_majflt_counter, ps->cpu_user_counter,
       ps->cpu_system_counter, ps->io_rchar, ps->io_wchar, ps->io_syscr,
       ps->io_syscw, ps->io_diskr, ps->io_diskw, ps->cswitch_vol,
-      ps->cswitch_invol);
+      ps->cswitch_invol, ps->delay_cpu, ps->delay_blkio, ps->delay_swapin,
+      ps->delay_freepages);
 
 } /* void ps_submit_proc_list */
 
@@ -1073,6 +1200,57 @@ static int ps_count_fd(int pid) {
   return (count >= 1) ? count : 1;
 } /* int ps_count_fd (pid) */
 
+#if HAVE_LIBTASKSTATS
+static int ps_delay(process_entry_t *ps) {
+  if (taskstats_handle == NULL) {
+    return ENOTCONN;
+  }
+
+  int status = ts_delay_by_tgid(taskstats_handle, (uint32_t)ps->id, &ps->delay);
+  if (status == EPERM) {
+    static c_complain_t c;
+#if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_NET_ADMIN)
+    if (check_capability(CAP_NET_ADMIN) != 0) {
+      if (getuid() == 0) {
+        c_complain(
+            LOG_ERR, &c,
+            "processes plugin: Reading Delay Accounting metric failed: %s. "
+            "collectd is running as root, but missing the CAP_NET_ADMIN "
+            "capability. The most common cause for this is that the init "
+            "system is dropping capabilities.",
+            STRERROR(status));
+      } else {
+        c_complain(
+            LOG_ERR, &c,
+            "processes plugin: Reading Delay Accounting metric failed: %s. "
+            "collectd is not running as root and missing the CAP_NET_ADMIN "
+            "capability. Either run collectd as root or grant it the "
+            "CAP_NET_ADMIN capability using \"setcap cap_net_admin=ep " PREFIX
+            "/sbin/collectd\".",
+            STRERROR(status));
+      }
+    } else {
+      ERROR("processes plugin: ts_delay_by_tgid failed: %s. The CAP_NET_ADMIN "
+            "capability is available (I checked), so this error is utterly "
+            "unexpected.",
+            STRERROR(status));
+    }
+#else
+    c_complain(LOG_ERR, &c,
+               "processes plugin: Reading Delay Accounting metric failed: %s. "
+               "Reading Delay Accounting metrics requires root privileges.",
+               STRERROR(status));
+#endif
+    return status;
+  } else if (status != 0) {
+    ERROR("processes plugin: ts_delay_by_tgid failed: %s", STRERROR(status));
+    return status;
+  }
+
+  return 0;
+}
+#endif
+
 static void ps_fill_details(const procstat_t *ps, process_entry_t *entry) {
   if (entry->has_io == 0) {
     ps_read_io(entry);
@@ -1101,8 +1279,17 @@ static void ps_fill_details(const procstat_t *ps, process_entry_t *entry) {
     }
     entry->has_fd = 1;
   }
+
+#if HAVE_LIBTASKSTATS
+  if (ps->report_delay && !entry->has_delay) {
+    if (ps_delay(entry) == 0) {
+      entry->has_delay = 1;
+    }
+  }
+#endif
 } /* void ps_fill_details (...) */
 
+/* ps_read_process reads process counters on Linux. */
 static int ps_read_process(long pid, process_entry_t *ps, char *state) {
   char filename[64];
   char buffer[1024];
@@ -1589,8 +1776,7 @@ static int mach_get_task_name(task_t t, int *pid, char *name,
   return 0;
 }
 #endif /* HAVE_THREAD_INFO */
-/* ------- end of additional functions for KERNEL_LINUX/HAVE_THREAD_INFO -------
- */
+/* end of additional functions for KERNEL_LINUX/HAVE_THREAD_INFO */
 
 /* do actual readings from kernel */
 static int ps_read(void) {
index e4419b8..85f7d87 100644 (file)
@@ -586,7 +586,8 @@ static int submit_counters(struct thread_data *t, struct core_data *c,
   /* If not using logical core numbering, set core id */
   if (!config_lcn) {
     if (topology.num_packages > 1)
-      snprintf(name, sizeof(name), "pkg%02d-core%02d", p->package_id, c->core_id);
+      snprintf(name, sizeof(name), "pkg%02d-core%02d", p->package_id,
+               c->core_id);
     else
       snprintf(name, sizeof(name), "core%02d", c->core_id);
   }
@@ -977,6 +978,7 @@ static int __attribute__((warn_unused_result)) probe_cpu(void) {
     case 0x45: /* HSW */
     case 0x46: /* HSW */
     case 0x3D: /* BDW */
+    case 0x5E: /* SKL */
       do_rapl = RAPL_PKG | RAPL_CORES | RAPL_GFX;
       break;
     case 0x3F: /* HSX */
index 4ca57aa..f27ba46 100644 (file)
@@ -44,6 +44,7 @@ current                 value:GAUGE:U:U
 current_connections     value:GAUGE:0:U
 current_sessions        value:GAUGE:0:U
 delay                   value:GAUGE:-1000000:1000000
+delay_rate              value:GAUGE:0:U
 derive                  value:DERIVE:0:U
 df                      used:GAUGE:0:1125899906842623, free:GAUGE:0:1125899906842623
 df_complex              value:GAUGE:0:U
index 79868fc..5134a6e 100644 (file)
@@ -118,10 +118,10 @@ static int latency_submit_match(cu_match_t *match, void *user_data) {
   sstrncpy(vl.type, data->type, sizeof(vl.type));
   for (size_t i = 0; i < data->latency_config.percentile_num; i++) {
     if (strlen(data->type_instance) != 0)
-      snprintf(vl.type_instance, sizeof(vl.type_instance), "%.117s-%.2f",
+      snprintf(vl.type_instance, sizeof(vl.type_instance), "%.50s-%.5g",
                data->type_instance, data->latency_config.percentile[i]);
     else
-      snprintf(vl.type_instance, sizeof(vl.type_instance), "%.0f",
+      snprintf(vl.type_instance, sizeof(vl.type_instance), "%.5g",
                data->latency_config.percentile[i]);
 
     vl.values = &(value_t){
@@ -150,11 +150,10 @@ static int latency_submit_match(cu_match_t *match, void *user_data) {
         bucket.upper_bound ? CDTIME_T_TO_DOUBLE(bucket.upper_bound) : INFINITY;
 
     if (strlen(data->type_instance) != 0)
-      snprintf(vl.type_instance, sizeof(vl.type_instance),
-               "%.54s-%.54s-%.2g_%.2g", data->type, data->type_instance,
-               lower_bound, upper_bound);
+      snprintf(vl.type_instance, sizeof(vl.type_instance), "%.50s-%.50s-%g_%g",
+               data->type, data->type_instance, lower_bound, upper_bound);
     else
-      snprintf(vl.type_instance, sizeof(vl.type_instance), "%.107s-%.2g_%.2g",
+      snprintf(vl.type_instance, sizeof(vl.type_instance), "%.50s-%g_%g",
                data->type, lower_bound, upper_bound);
 
     vl.values = &(value_t){
diff --git a/src/utils_taskstats.c b/src/utils_taskstats.c
new file mode 100644 (file)
index 0000000..f0d7333
--- /dev/null
@@ -0,0 +1,306 @@
+/**
+ * collectd - src/utils_taskstats.c
+ * Copyright (C) 2017       Florian octo Forster
+ *
+ * ISC License (ISC)
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ */
+
+#include "collectd.h"
+#include "utils_taskstats.h"
+
+#include "common.h"
+#include "plugin.h"
+#include "utils_time.h"
+
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+#include <linux/taskstats.h>
+
+struct ts_s {
+  struct mnl_socket *nl;
+  pid_t pid;
+  uint32_t seq;
+  uint16_t genl_id_taskstats;
+  unsigned int port_id;
+};
+
+/* nlmsg_errno returns the errno encoded in nlh or zero if not an error. */
+static int nlmsg_errno(struct nlmsghdr *nlh, size_t sz) {
+  if (!mnl_nlmsg_ok(nlh, (int)sz)) {
+    ERROR("utils_taskstats: mnl_nlmsg_ok failed.");
+    return EPROTO;
+  }
+
+  if (nlh->nlmsg_type != NLMSG_ERROR) {
+    return 0;
+  }
+
+  struct nlmsgerr *nlerr = mnl_nlmsg_get_payload(nlh);
+  /* (struct nlmsgerr).error holds a negative errno. */
+  return nlerr->error * (-1);
+}
+
+static int get_taskstats_attr_cb(const struct nlattr *attr, void *data) {
+  struct taskstats *ret_taskstats = data;
+
+  uint16_t type = mnl_attr_get_type(attr);
+  switch (type) {
+  case TASKSTATS_TYPE_STATS:
+    if (mnl_attr_get_payload_len(attr) != sizeof(*ret_taskstats)) {
+      ERROR("utils_taskstats: mnl_attr_get_payload_len(attr) = %" PRIu32
+            ", want %zu",
+            mnl_attr_get_payload_len(attr), sizeof(*ret_taskstats));
+      return MNL_CB_ERROR;
+    }
+    struct taskstats *ts = mnl_attr_get_payload(attr);
+    memmove(ret_taskstats, ts, sizeof(*ret_taskstats));
+    return MNL_CB_OK;
+
+  case TASKSTATS_TYPE_AGGR_PID: /* fall through */
+  case TASKSTATS_TYPE_AGGR_TGID:
+    return mnl_attr_parse_nested(attr, get_taskstats_attr_cb, ret_taskstats);
+
+  case TASKSTATS_TYPE_PID: /* fall through */
+  case TASKSTATS_TYPE_TGID:
+    /* ignore */
+    return MNL_CB_OK;
+
+  default:
+    DEBUG("utils_taskstats: unknown attribute %" PRIu16
+          ", want one of TASKSTATS_TYPE_AGGR_PID/TGID, TASKSTATS_TYPE_STATS",
+          type);
+  }
+  return MNL_CB_OK;
+}
+
+static int get_taskstats_msg_cb(const struct nlmsghdr *nlh, void *data) {
+  return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_taskstats_attr_cb,
+                        data);
+}
+
+static int get_taskstats(ts_t *ts, uint32_t tgid,
+                         struct taskstats *ret_taskstats) {
+  char buffer[MNL_SOCKET_BUFFER_SIZE];
+  uint32_t seq = ts->seq++;
+
+  struct nlmsghdr *nlh = mnl_nlmsg_put_header(buffer);
+  *nlh = (struct nlmsghdr){
+      .nlmsg_len = nlh->nlmsg_len,
+      .nlmsg_type = ts->genl_id_taskstats,
+      .nlmsg_flags = NLM_F_REQUEST,
+      .nlmsg_seq = seq,
+      .nlmsg_pid = ts->pid,
+  };
+
+  struct genlmsghdr *genh = mnl_nlmsg_put_extra_header(nlh, sizeof(*genh));
+  *genh = (struct genlmsghdr){
+      .cmd = TASKSTATS_CMD_GET,
+      .version = TASKSTATS_GENL_VERSION, // or TASKSTATS_VERSION?
+  };
+
+  // mnl_attr_put_u32(nlh, TASKSTATS_CMD_ATTR_PID, tgid);
+  mnl_attr_put_u32(nlh, TASKSTATS_CMD_ATTR_TGID, tgid);
+
+  if (mnl_socket_sendto(ts->nl, nlh, nlh->nlmsg_len) < 0) {
+    int status = errno;
+    ERROR("utils_taskstats: mnl_socket_sendto() = %s", STRERROR(status));
+    return status;
+  }
+
+  int status = mnl_socket_recvfrom(ts->nl, buffer, sizeof(buffer));
+  if (status < 0) {
+    status = errno;
+    ERROR("utils_taskstats: mnl_socket_recvfrom() = %s", STRERROR(status));
+    return status;
+  } else if (status == 0) {
+    ERROR("utils_taskstats: mnl_socket_recvfrom() = 0");
+    return ECONNABORTED;
+  }
+  size_t buffer_size = (size_t)status;
+
+  if ((status = nlmsg_errno((void *)buffer, buffer_size)) != 0) {
+    ERROR("utils_taskstats: TASKSTATS_CMD_GET(TASKSTATS_CMD_ATTR_TGID = "
+          "%" PRIu32 ") = %s",
+          (uint32_t)tgid, STRERROR(status));
+    return status;
+  }
+
+  status = mnl_cb_run(buffer, buffer_size, seq, ts->port_id,
+                      get_taskstats_msg_cb, ret_taskstats);
+  if (status < MNL_CB_STOP) {
+    ERROR("utils_taskstats: Parsing message failed.");
+    return EPROTO;
+  }
+
+  return 0;
+}
+
+static int get_family_id_attr_cb(const struct nlattr *attr, void *data) {
+  uint16_t type = mnl_attr_get_type(attr);
+  if (type != CTRL_ATTR_FAMILY_ID) {
+    return MNL_CB_OK;
+  }
+
+  if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) {
+    ERROR("mnl_attr_validate() = %s", STRERRNO);
+    return MNL_CB_ERROR;
+  }
+
+  uint16_t *ret_family_id = data;
+  *ret_family_id = mnl_attr_get_u16(attr);
+  return MNL_CB_STOP;
+}
+
+static int get_family_id_msg_cb(const struct nlmsghdr *nlh, void *data) {
+  return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb,
+                        data);
+}
+
+/* get_family_id initializes ts->genl_id_taskstats. Returns 0 on success and
+ * an error code otherwise. */
+static int get_family_id(ts_t *ts) {
+  char buffer[MNL_SOCKET_BUFFER_SIZE];
+  uint32_t seq = ts->seq++;
+
+  struct nlmsghdr *nlh = mnl_nlmsg_put_header(buffer);
+  *nlh = (struct nlmsghdr){
+      .nlmsg_len = nlh->nlmsg_len,
+      .nlmsg_type = GENL_ID_CTRL,
+      .nlmsg_flags = NLM_F_REQUEST,
+      .nlmsg_seq = seq,
+      .nlmsg_pid = ts->pid,
+  };
+
+  struct genlmsghdr *genh = mnl_nlmsg_put_extra_header(nlh, sizeof(*genh));
+  *genh = (struct genlmsghdr){
+      .cmd = CTRL_CMD_GETFAMILY, .version = 0x01,
+  };
+
+  mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, TASKSTATS_GENL_NAME);
+
+  assert(genh->cmd == CTRL_CMD_GETFAMILY);
+  assert(genh->version == TASKSTATS_GENL_VERSION);
+
+  if (mnl_socket_sendto(ts->nl, nlh, nlh->nlmsg_len) < 0) {
+    int status = errno;
+    ERROR("utils_taskstats: mnl_socket_sendto() = %s", STRERROR(status));
+    return status;
+  }
+
+  ts->genl_id_taskstats = 0;
+  while (42) {
+    int status = mnl_socket_recvfrom(ts->nl, buffer, sizeof(buffer));
+    if (status < 0) {
+      status = errno;
+      ERROR("utils_taskstats: mnl_socket_recvfrom() = %s", STRERROR(status));
+      return status;
+    } else if (status == 0) {
+      break;
+    }
+    size_t buffer_size = (size_t)status;
+
+    if ((status = nlmsg_errno((void *)buffer, buffer_size)) != 0) {
+      ERROR("utils_taskstats: CTRL_CMD_GETFAMILY(\"%s\"): %s",
+            TASKSTATS_GENL_NAME, STRERROR(status));
+      return status;
+    }
+
+    status = mnl_cb_run(buffer, buffer_size, seq, ts->port_id,
+                        get_family_id_msg_cb, &ts->genl_id_taskstats);
+    if (status < MNL_CB_STOP) {
+      ERROR("utils_taskstats: Parsing message failed.");
+      return EPROTO;
+    } else if (status == MNL_CB_STOP) {
+      break;
+    }
+  }
+
+  if (ts->genl_id_taskstats == 0) {
+    ERROR("utils_taskstats: Netlink communication succeeded, but "
+          "genl_id_taskstats is still zero.");
+    return ENOENT;
+  }
+
+  return 0;
+}
+
+void ts_destroy(ts_t *ts) {
+  if (ts == NULL) {
+    return;
+  }
+
+  if (ts->nl != NULL) {
+    mnl_socket_close(ts->nl);
+    ts->nl = NULL;
+  }
+
+  sfree(ts);
+}
+
+ts_t *ts_create(void) {
+  ts_t *ts = calloc(1, sizeof(*ts));
+  if (ts == NULL) {
+    ERROR("utils_taskstats: calloc failed: %s", STRERRNO);
+    return NULL;
+  }
+
+  if ((ts->nl = mnl_socket_open(NETLINK_GENERIC)) == NULL) {
+    ERROR("utils_taskstats: mnl_socket_open(NETLINK_GENERIC) = %s", STRERRNO);
+    ts_destroy(ts);
+    return NULL;
+  }
+
+  if (mnl_socket_bind(ts->nl, 0, MNL_SOCKET_AUTOPID) != 0) {
+    ERROR("utils_taskstats: mnl_socket_bind() = %s", STRERRNO);
+    ts_destroy(ts);
+    return NULL;
+  }
+
+  ts->pid = getpid();
+  ts->port_id = mnl_socket_get_portid(ts->nl);
+
+  int status = get_family_id(ts);
+  if (status != 0) {
+    ERROR("utils_taskstats: get_family_id() = %s", STRERROR(status));
+    ts_destroy(ts);
+    return NULL;
+  }
+
+  return ts;
+}
+
+int ts_delay_by_tgid(ts_t *ts, uint32_t tgid, ts_delay_t *out) {
+  if ((ts == NULL) || (out == NULL)) {
+    return EINVAL;
+  }
+
+  struct taskstats raw = {0};
+
+  int status = get_taskstats(ts, tgid, &raw);
+  if (status != 0) {
+    return status;
+  }
+
+  *out = (ts_delay_t){
+      .cpu_ns = raw.cpu_delay_total,
+      .blkio_ns = raw.blkio_delay_total,
+      .swapin_ns = raw.swapin_delay_total,
+      .freepages_ns = raw.freepages_delay_total,
+  };
+  return 0;
+}
diff --git a/src/utils_taskstats.h b/src/utils_taskstats.h
new file mode 100644 (file)
index 0000000..de07427
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * collectd - src/utils_taskstats.h
+ * Copyright (C) 2017       Florian octo Forster
+ *
+ * ISC License (ISC)
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ */
+
+#ifndef UTILS_TASKSTATS_H
+#define UTILS_TASKSTATS_H 1
+
+#include "collectd.h"
+
+#include "utils_time.h"
+
+struct ts_s;
+typedef struct ts_s ts_t;
+
+typedef struct {
+  uint64_t cpu_ns;
+  uint64_t blkio_ns;
+  uint64_t swapin_ns;
+  uint64_t freepages_ns;
+} ts_delay_t;
+
+ts_t *ts_create(void);
+void ts_destroy(ts_t *);
+
+/* ts_delay_by_tgid returns Linux delay accounting information for the task
+ * identified by tgid. Returns zero on success and an errno otherwise. */
+int ts_delay_by_tgid(ts_t *ts, uint32_t tgid, ts_delay_t *out);
+
+#endif /* UTILS_TASKSTATS_H */