Merge branch 'collectd-4.9' into collectd-4.10
authorFlorian Forster <octo@huhu.verplant.org>
Mon, 14 Mar 2011 19:06:53 +0000 (20:06 +0100)
committerFlorian Forster <octo@huhu.verplant.org>
Mon, 14 Mar 2011 19:06:53 +0000 (20:06 +0100)
1  2 
src/collectd.conf.pod
src/plugin.c
src/processes.c
src/python.c

diff --combined src/collectd.conf.pod
@@@ -133,16 -133,6 +133,16 @@@ B<Warning:> You should set this once an
  I<you will have to delete all your RRD files> or know some serious RRDtool
  magic! (Assuming you're using the I<RRDtool> or I<RRDCacheD> plugin.)
  
 +=item B<Timeout> I<Iterations>
 +
 +Consider a value list "missing" when no update has been read or received for
 +I<Iterations> iterations. By default, I<collectd> considers a value list
 +missing when no update has been received for twice the update interval. Since
 +this setting uses iterations, the maximum allowed time without update depends
 +on the I<Interval> information contained in each value list. This is used in
 +the I<Threshold> configuration to dispatch notifications about missing values,
 +see L<"THRESHOLD CONFIGURATION"> below.
 +
  =item B<ReadThreads> I<Num>
  
  Number of threads to start for reading plugins. The default value is B<5>, but
@@@ -660,110 -650,6 +660,110 @@@ Type-instance to use. Defaults to the c
  
  =back
  
 +=head2 Plugin C<curl_xml>
 +
 +The B<curl_xml plugin> uses B<libcurl> (L<http://curl.haxx.se/>) and B<libxml2>
 +(L<http://xmlsoft.org/>) to retrieve XML data via cURL.
 +
 + <Plugin "curl_xml">
 +   <URL "http://localhost/stats.xml">
 +     Host "my_host"
 +     Instance "some_instance"
 +     User "collectd"
 +     Password "thaiNg0I"
 +     VerifyPeer true
 +     VerifyHost true
 +     CACert "/path/to/ca.crt"
 +
 +     <XPath "table[@id=\"magic_level\"]/tr">
 +       Type "magic_level"
 +       #InstancePrefix "prefix-"
 +       InstanceFrom "td[1]"
 +       ValuesFrom "td[2]/span[@class=\"level\"]"
 +     </XPath>
 +   </URL>
 + </Plugin>
 +
 +In the B<Plugin> block, there may be one or more B<URL> blocks, each defining a
 +URL to be fetched via HTTP (using libcurl). Within each B<URL> block there are
 +options which specify the connection parameters, for example authentication
 +information, and one or more B<XPath> blocks.
 +
 +Each B<XPath> block specifies how to get one type of information. The
 +string argument must be a valid XPath expression which returns a list
 +of "base elements". One value is dispatched for each "base element". The
 +I<type instance> and values are looked up using further I<XPath> expressions
 +that should be relative to the base element.
 +
 +Within the B<URL> block the following options are accepted:
 +
 +=over 4
 +
 +=item B<Host> I<Name>
 +
 +Use I<Name> as the host name when submitting values. Defaults to the global
 +host name setting.
 +
 +=item B<Instance> I<Instance>
 +
 +Use I<Instance> as the plugin instance when submitting values. Defaults to an
 +empty string (no plugin instance).
 +
 +=item B<User> I<User>
 +=item B<Password> I<Password>
 +=item B<VerifyPeer> B<true>|B<false>
 +=item B<VerifyHost> B<true>|B<false>
 +=item B<CACert> I<CA Cert File>
 +
 +These options behave exactly equivalent to the appropriate options of the
 +I<cURL> and I<cURL-JSON> plugins. Please see there for a detailed description.
 +
 +=item E<lt>B<XPath> I<XPath-expression>E<gt>
 +
 +Within each B<URL> block, there must be one or more B<XPath> blocks. Each
 +B<XPath> block specifies how to get one type of information. The string
 +argument must be a valid XPath expression which returns a list of "base
 +elements". One value is dispatched for each "base element".
 +
 +Within the B<XPath> block the following options are accepted:
 +
 +=over 4
 +
 +=item B<Type> I<Type>
 +
 +Specifies the I<Type> used for submitting patches. This determines the number
 +of values that are required / expected and whether the strings are parsed as
 +signed or unsigned integer or as double values. See L<types.db(5)> for details.
 +This option is required.
 +
 +=item B<InstancePrefix> I<InstancePrefix>
 +
 +Prefix the I<type instance> with I<InstancePrefix>. The values are simply
 +concatenated together without any separator.
 +This option is optional.
 +
 +=item B<InstanceFrom> I<InstanceFrom>
 +
 +Specifies a XPath expression to use for determining the I<type instance>. The
 +XPath expression must return exactly one element. The element's value is then
 +used as I<type instance>, possibly prefixed with I<InstancePrefix> (see above).
 +
 +This value is required. As a special exception, if the "base XPath expression"
 +(the argument to the B<XPath> block) returns exactly one argument, then this
 +option may be omitted.
 +
 +=item B<ValuesFrom> I<ValuesFrom> [I<ValuesFrom> ...]
 +
 +Specifies one or more XPath expression to use for reading the values. The
 +number of XPath expressions must match the number of data sources in the
 +I<type> specified with B<Type> (see above). Each XPath expression must return
 +exactly one element. The element's value is then parsed as a number and used as
 +value for the appropriate value in the value list dispatched to the daemon.
 +
 +=back
 +
 +=back
 +
  =head2 Plugin C<dbi>
  
  This plugin uses the B<dbi> library (L<http://libdbi.sourceforge.net/>) to
@@@ -1249,12 -1135,6 +1249,12 @@@ note that there are 1000 bytes in a kil
  
  Controls whether or not to recurse into subdirectories. Enabled by default.
  
 +=item B<IncludeHidden> I<true>|I<false>
 +
 +Controls whether or not to include "hidden" files and directories in the count.
 +"Hidden" files and directories are those, whose name begins with a dot.
 +Defaults to I<false>, i.e. by default hidden files and directories are ignored.
 +
  =back
  
  =head2 Plugin C<GenericJMX>
@@@ -1624,11 -1504,6 +1624,11 @@@ running in foreground- or non-daemon-mo
  
  Prefix all lines printed by the current time. Defaults to B<true>.
  
 +=item B<PrintSeverity> B<true>|B<false>
 +
 +When enabled, all lines are prefixed by the severity of the log message, for
 +example "warning". Defaults to B<false>.
 +
  =back
  
  B<Note>: There is no need to notify the daemon after moving or removing the
@@@ -1729,132 -1604,6 +1729,132 @@@ TCP-Port to connect to. Defaults to B<1
  
  =back
  
 +=head2 Plugin C<modbus>
 +
 +The B<modbus plugin> connects to a Modbus "slave" via Modbus/TCP and reads
 +register values. It supports reading single registers (unsigned 16E<nbsp>bit
 +values), large integer values (unsigned 32E<nbsp>bit values) and floating point
 +values (two registers interpreted as IEEE floats in big endian notation).
 +
 +Synopsis:
 +
 + <Data "voltage-input-1">
 +   RegisterBase 0
 +   RegisterType float
 +   Type voltage
 +   Instance "input-1"
 + </Data>
 + 
 + <Data "voltage-input-2">
 +   RegisterBase 2
 +   RegisterType float
 +   Type voltage
 +   Instance "input-2"
 + </Data>
 + 
 + <Host "modbus.example.com">
 +   Address "192.168.0.42"
 +   Port    "502"
 +   Interval 60
 +   
 +   <Slave 1>
 +     Instance "power-supply"
 +     Collect  "voltage-input-1"
 +     Collect  "voltage-input-2"
 +   </Slave>
 + </Host>
 +
 +=over 4
 +
 +=item E<lt>B<Data> I<Name>E<gt> blocks
 +
 +Data blocks define a mapping between register numbers and the "types" used by
 +I<collectd>.
 +
 +Within E<lt>DataE<nbsp>/E<gt> blocks, the following options are allowed:
 +
 +=over 4
 +
 +=item B<RegisterBase> I<Number>
 +
 +Configures the base register to read from the device. If the option
 +B<RegisterType> has been set to B<Uint32> or B<Float>, this and the next
 +register will be read (the register number is increased by one).
 +
 +=item B<RegisterType> B<Uint16>|B<Uint32>|B<Float>
 +
 +Specifies what kind of data is returned by the device. If the type is B<Uint32>
 +or B<Float>, two 16E<nbsp>bit registers will be read and the data is combined
 +into one value. Defaults to B<Uint16>.
 +
 +=item B<Type> I<Type>
 +
 +Specifies the "type" (data set) to use when dispatching the value to
 +I<collectd>. Currently, only data sets with exactly one data source are
 +supported.
 +
 +=item B<Instance> I<Instance>
 +
 +Sets the type instance to use when dispatching the value to I<collectd>. If
 +unset, an empty string (no type instance) is used.
 +
 +=back
 +
 +=item E<lt>B<Host> I<Name>E<gt> blocks
 +
 +Host blocks are used to specify to which hosts to connect and what data to read
 +from their "slaves". The string argument I<Name> is used as hostname when
 +dispatching the values to I<collectd>.
 +
 +Within E<lt>HostE<nbsp>/E<gt> blocks, the following options are allowed:
 +
 +=over 4
 +
 +=item B<Address> I<Hostname>
 +
 +Specifies the node name (the actual network address) used to connect to the
 +host. This may be an IP address or a hostname. Please note that the used
 +I<libmodbus> library only supports IPv4 at the moment.
 +
 +=item B<Port> I<Service>
 +
 +Specifies the port used to connect to the host. The port can either be given as
 +a number or as a service name. Please note that the I<Service> argument must be
 +a string, even if ports are given in their numerical form. Defaults to "502".
 +
 +=item B<Interval> I<Interval>
 +
 +Sets the interval (in seconds) in which the values will be collected from this
 +host. By default the global B<Interval> setting will be used.
 +
 +=item E<lt>B<Slave> I<ID>E<gt>
 +
 +Over each TCP connection, multiple Modbus devices may be reached. The slave ID
 +is used to specify which device should be addressed. For each device you want
 +to query, one B<Slave> block must be given.
 +
 +Within E<lt>SlaveE<nbsp>/E<gt> blocks, the following options are allowed:
 +
 +=over 4
 +
 +=item B<Instance> I<Instance>
 +
 +Specify the plugin instance to use when dispatching the values to I<collectd>.
 +By default "slave_I<ID>" is used.
 +
 +=item B<Collect> I<DataName>
 +
 +Specifies which data to retrieve from the device. I<DataName> must be the same
 +string as the I<Name> argument passed to a B<Data> block. You can specify this
 +option multiple times to collect more than one value from a slave. At least one
 +B<Collect> option is mandatory.
 +
 +=back
 +
 +=back
 +
 +=back
 +
  =head2 Plugin C<mysql>
  
  The C<mysql plugin> requires B<mysqlclient> to be installed. It connects to
@@@ -1911,8 -1660,10 +1911,10 @@@ Hostname of the database server. Defaul
  =item B<User> I<Username>
  
  Username to use when connecting to the database. The user does not have to be
- granted any privileges (which is synonym to granting the C<USAGE> privilege).
- Any existing MySQL user will do.
+ granted any privileges (which is synonym to granting the C<USAGE> privilege),
+ unless you want to collectd replication statistics (see B<MasterStats> and
+ B<SlaveStats> below). In this case, the user needs the C<REPLICATION CLIENT>
+ (or C<SUPER>) privileges. Else, any existing MySQL user will do.
  
  =item B<Password> I<Password>
  
@@@ -1944,7 -1695,9 +1946,9 @@@ C<mysql_real_connect> function for deta
  
  =item B<SlaveStats> I<true|false>
  
- Enable the collection of master / slave statistics in a replication setup.
+ Enable the collection of master / slave statistics in a replication setup. In
+ order to be able to get access to these statistics, the user needs special
+ privileges. See the B<User> documentation above.
  
  =item B<SlaveNotifications> I<true|false>
  
@@@ -2553,15 -2306,6 +2557,15 @@@ B<None> require this setting
  This feature is only available if the I<network> plugin was linked with
  I<libgcrypt>.
  
 +=item B<Interface> I<Interface name>
 +
 +Set the outgoing interface for IP packets. This applies at least
 +to IPv6 packets and if possible to IPv4. If this option is not applicable,
 +undefined or a non-existent interface name is specified, the default
 +behavior is to let the kernel choose the appropriate interface. Be warned
 +that the manual selection of an interface for unicast traffic is only
 +necessary in rare cases.
 +
  =back
  
  =item B<E<lt>Listen> I<Host> [I<Port>]B<E<gt>>
@@@ -2610,14 -2354,6 +2614,14 @@@ Each time a packet is received, the mod
  using L<stat(2)>. If the file has been changed, the contents is re-read. While
  the file is being read, it is locked using L<fcntl(2)>.
  
 +=item B<Interface> I<Interface name>
 +
 +Set the incoming interface for IP packets explicitly. This applies at least
 +to IPv6 packets and if possible to IPv4. If this option is not applicable,
 +undefined or a non-existent interface name is specified, the default
 +behavior is, to let the kernel choose the appropriate interface. Thus incoming
 +traffic gets only accepted, if it arrives on the given interface.
 +
  =back
  
  =item B<TimeToLive> I<1-255>
@@@ -2712,7 -2448,8 +2716,8 @@@ and are checked by default depends on t
  This plugin sends a desktop notification to a notification daemon, as defined
  in the Desktop Notification Specification. To actually display the
  notifications, B<notification-daemon> is required and B<collectd> has to be
- able to access the X server.
+ able to access the X server (i.E<nbsp>e., the C<DISPLAY> and C<XAUTHORITY>
+ environment variables have to be set correctly) and the D-Bus message bus.
  
  The Desktop Notification Specification can be found at
  L<http://www.galago-project.org/specs/notification/>.
@@@ -2967,24 -2704,12 +2972,24 @@@ and the client's "common name" will be 
  when reading multiple status files. Enabling this option is recommended, but to
  maintain backwards compatibility this option is disabled by default.
  
 -=item B<Compression> B<true>|B<false>
 +=item B<CollectCompression> B<true>|B<false>
  
  Sets whether or not statistics about the compression used by OpenVPN should be
  collected. This information is only available in I<single> mode. Enabled by
  default.
  
 +=item B<CollectIndividualUsers> B<true>|B<false>
 +
 +Sets whether or not traffic information is collected for each connected client
 +individually. If set to false, currently no traffic data is collected at all
 +because aggregating this data in a save manner is tricky. Defaults to B<true>.
 +
 +=item B<CollectUserCount> B<true>|B<false>
 +
 +When enabled, the number of currently connected clients or users is collected.
 +This is especially interesting when B<CollectIndividualUsers> is disabled, but
 +can be configured independently from that option. Defaults to B<false>.
 +
  =back
  
  =head2 Plugin C<oracle>
@@@ -3054,83 -2779,6 +3059,83 @@@ refer to them from
  This plugin embeds a Perl-interpreter into collectd and provides an interface
  to collectd's plugin system. See L<collectd-perl(5)> for its documentation.
  
 +=head2 Plugin C<pinba>
 +
 +The I<Pinba plugin> receives profiling information from I<Pinba>, an extension
 +for the I<PHP> interpreter. At the end of executing a script, i.e. after a
 +PHP-based webpage has been delivered, the extension will send a UDP packet
 +containing timing information, peak memory usage and so on. The plugin will
 +wait for such packets, parse them and account the provided information, which
 +is then dispatched to the daemon once per interval.
 +
 +Synopsis:
 +
 + <Plugin pinba>
 +   Address "::0"
 +   Port "30002"
 +   # Overall statistics for the website.
 +   <View "www-total">
 +     Server "www.example.com"
 +   </View>
 +   # Statistics for www-a only
 +   <View "www-a">
 +     Host "www-a.example.com"
 +     Server "www.example.com"
 +   </View>
 +   # Statistics for www-b only
 +   <View "www-b">
 +     Host "www-b.example.com"
 +     Server "www.example.com"
 +   </View>
 + </Plugin>
 +
 +The plugin provides the following configuration options:
 +
 +=over 4
 +
 +=item B<Address> I<Node>
 +
 +Configures the address used to open a listening socket. By default, plugin will
 +bind to the I<any> address C<::0>.
 +
 +=item B<Port> I<Service>
 +
 +Configures the port (service) to bind to. By default the default Pinba port
 +"30002" will be used. The option accepts service names in addition to port
 +numbers and thus requires a I<string> argument.
 +
 +=item E<lt>B<View> I<Name>E<gt> block
 +
 +The packets sent by the Pinba extension include the hostname of the server, the
 +server name (the name of the virtual host) and the script that was executed.
 +Using B<View> blocks it is possible to separate the data into multiple groups
 +to get more meaningful statistics. Each packet is added to all matching groups,
 +so that a packet may be accounted for more than once.
 +
 +=over 4
 +
 +=item B<Host> I<Host>
 +
 +Matches the hostname of the system the webserver / script is running on. This
 +will contain the result of the L<gethostname(2)> system call. If not
 +configured, all hostnames will be accepted.
 +
 +=item B<Server> I<Server>
 +
 +Matches the name of the I<virtual host>, i.e. the contents of the
 +C<$_SERVER["SERVER_NAME"]> variable when within PHP. If not configured, all
 +server names will be accepted.
 +
 +=item B<Script> I<Script>
 +
 +Matches the name of the I<script name>, i.e. the contents of the
 +C<$_SERVER["SCRIPT_NAME"]> variable when within PHP. If not configured, all
 +script names will be accepted.
 +
 +=back
 +
 +=back
 +
  =head2 Plugin C<ping>
  
  The I<Ping> plugin starts a new thread which sends ICMP "ping" packets to the
@@@ -3184,7 -2832,7 +3189,7 @@@ operating systems
  
  =item B<MaxMissed> I<Packets>
  
 -Trigger a DNS resolv after the host has not replied to I<Packets> packets. This
 +Trigger a DNS resolve after the host has not replied to I<Packets> packets. This
  enables the use of dynamic DNS services (like dyndns.org) with the ping plugin.
  
  Default: B<-1> (disabled)
@@@ -3249,7 -2897,6 +3254,7 @@@ L<http://www.postgresql.org/docs/manual
      </Database>
  
      <Database bar>
 +      Interval 300
        Service "service_name"
        Query backend # predefined
        Query rt36_tickets
@@@ -3313,8 -2960,7 +3318,8 @@@ The username used to connect to the dat
  
  =item I<interval>
  
 -The interval collectd is using (as specified by the B<Interval> option).
 +The interval with which this database is queried (as specified by the database
 +specific or global B<Interval> options).
  
  =back
  
@@@ -3452,11 -3098,6 +3457,11 @@@ for details
  
  =over 4
  
 +=item B<Interval> I<seconds>
 +
 +Specify the interval with which the database should be queried. The default is
 +to use the global B<Interval> setting.
 +
  =item B<Host> I<hostname>
  
  Specify the hostname or IP of the PostgreSQL server to connect to. If the
@@@ -3737,8 -3378,6 +3742,8 @@@ multiple routers
        User "collectd"
        Password "secr3t"
        CollectInterface true
 +      CollectCPULoad true
 +      CollectMemory true
      </Router>
      <Router>
        Host "router1.example.com"
        Password "5ecret"
        CollectInterface true
        CollectRegistrationTable true
 +      CollectDF true
 +      CollectDisk true
      </Router>
    </Plugin>
  
@@@ -3785,29 -3422,6 +3790,29 @@@ present on the device. Defaults to B<fa
  When set to B<true>, information about wireless LAN connections will be
  collected. Defaults to B<false>.
  
 +=item B<CollectCPULoad> B<true>|B<false>
 +
 +When set to B<true>, information about the CPU usage will be collected. The
 +number is a dimensionless value where zero indicates no CPU usage at all.
 +Defaults to B<false>.
 +
 +=item B<CollectMemory> B<true>|B<false>
 +
 +When enabled, the amount of used and free memory will be collected. How used
 +memory is calculated is unknown, for example whether or not caches are counted
 +as used space.
 +Defaults to B<false>.
 +
 +=item B<CollectDF> B<true>|B<false>
 +
 +When enabled, the amount of used and free disk space will be collected.
 +Defaults to B<false>.
 +
 +=item B<CollectDisk> B<true>|B<false>
 +
 +When enabled, the number of sectors written and bad blocks will be collected.
 +Defaults to B<false>.
 +
  =back
  
  =head2 Plugin C<rrdcached>
@@@ -4140,7 -3754,6 +4145,7 @@@ user using (extended) regular expressio
        </Match>
        <Match>
          Regex "\\<R=local_user\\>"
 +        ExcludeRegex "\\<R=local_user\\>.*mail_spool defer"
          DSType "CounterInc"
          Type "counter"
          Instance "local_user"
@@@ -4175,13 -3788,6 +4180,13 @@@ want to match literal parentheses you n
  
    Regex "SPAM \\(Score: (-?[0-9]+\\.[0-9]+)\\)"
  
 +=item B<ExcludeRegex> I<regex>
 +
 +Sets an optional regular expression to use for excluding lines from the match.
 +An example which excludes all connections from localhost from the match:
 +
 +  ExcludeRegex "127\\.0\\.0\\.1"
 +
  =item B<DSType> I<Type>
  
  Sets how the values are cumulated. I<Type> is one of:
@@@ -4549,12 -4155,6 +4554,12 @@@ create output in the I<JavaScript Objec
  
  Defaults to B<Command>.
  
 +=item B<StoreRates> B<true|false>
 +
 +If set to B<true>, convert counter values to rates. If set to B<false> (the
 +default) counter values are stored as is, i.E<nbsp>e. as an increasing integer
 +number.
 +
  =back
  
  =head1 THRESHOLD CONFIGURATION
@@@ -4576,12 -4176,10 +4581,12 @@@ as a moving average or similar - at lea
  
  Also, all values that match a threshold are considered to be relevant or
  "interesting". As a consequence collectd will issue a notification if they are
 -not received for twice the last timeout of the values. If, for example, some
 -hosts sends it's CPU statistics to the server every 60 seconds, a notification
 -will be dispatched after about 120 seconds. It may take a little longer because
 -the timeout is checked only once each B<Interval> on the server.
 +not received for B<Timeout> iterations. The B<Timeout> configuration option is
 +explained in section L<"GLOBAL OPTIONS">. If, for example, B<Timeout> is set to
 +"2" (the default) and some hosts sends it's CPU statistics to the server every
 +60 seconds, a notification will be dispatched after about 120 seconds. It may
 +take a little longer because the timeout is checked only once each B<Interval>
 +on the server.
  
  When a value comes within range again or is received after it was missing, an
  "OKAY-notification" is dispatched.
@@@ -5064,12 -4662,6 +5069,12 @@@ Match values where the given regular ex
  the identifier of a value. If multiple regular expressions are given, B<all>
  regexen must match for a value to match.
  
 +=item B<Invert> B<false>|B<true>
 +
 +When set to B<true>, the result of the match is inverted, i.e. all value lists
 +where all regular expressions apply are not matched, all other value lists are
 +matched. Defaults to B<false>.
 +
  =back
  
  Example:
diff --combined src/plugin.c
@@@ -59,7 -59,6 +59,7 @@@ struct read_func_
  #define rf_callback rf_super.cf_callback
  #define rf_udata rf_super.cf_udata
        callback_func_t rf_super;
 +      char rf_group[DATA_MAX_NAME_LEN];
        char rf_name[DATA_MAX_NAME_LEN];
        int rf_type;
        struct timespec rf_interval;
@@@ -280,8 -279,6 +280,6 @@@ static int plugin_load_file (char *file
        lt_dlhandle dlh;
        void (*reg_handle) (void);
  
-       DEBUG ("file = %s", file);
        lt_dlinit ();
        lt_dlerror (); /* clear errors */
  
        }
  #else /* if LIBTOOL_VERSION == 1 */
        if (flags & PLUGIN_FLAGS_GLOBAL)
-               ERROR ("plugin_load_file: The global flag is not supported, "
+               WARNING ("plugin_load_file: The global flag is not supported, "
                                "libtool 2 is required for this.");
        dlh = lt_dlopen (file);
  #endif
  
        if (dlh == NULL)
        {
-               const char *error = lt_dlerror ();
+               char errbuf[1024] = "";
+               ssnprintf (errbuf, sizeof (errbuf),
+                               "lt_dlopen (\"%s\") failed: %s. "
+                               "The most common cause for this problem are "
+                               "missing dependencies. Use ldd(1) to check "
+                               "the dependencies of the plugin "
+                               "/ shared object.",
+                               file, lt_dlerror ());
+               ERROR ("%s", errbuf);
+               /* Make sure this is printed to STDERR in any case, but also
+                * make sure it's printed only once. */
+               if (list_log != NULL)
+                       fprintf (stderr, "ERROR: %s\n", errbuf);
  
-               ERROR ("lt_dlopen (%s) failed: %s", file, error);
-               fprintf (stderr, "lt_dlopen (%s) failed: %s\n", file, error);
                return (1);
        }
  
        if ((reg_handle = (void (*) (void)) lt_dlsym (dlh, "module_register")) == NULL)
        {
-               WARNING ("Couldn't find symbol `module_register' in `%s': %s\n",
+               WARNING ("Couldn't find symbol \"module_register\" in \"%s\": %s\n",
                                file, lt_dlerror ());
                lt_dlclose (dlh);
                return (-1);
@@@ -776,7 -785,6 +786,7 @@@ int plugin_register_read (const char *n
        rf->rf_callback = (void *) callback;
        rf->rf_udata.data = NULL;
        rf->rf_udata.free_func = NULL;
 +      rf->rf_group[0] = '\0';
        sstrncpy (rf->rf_name, name, sizeof (rf->rf_name));
        rf->rf_type = RF_SIMPLE;
        rf->rf_interval.tv_sec = 0;
        return (plugin_insert_read (rf));
  } /* int plugin_register_read */
  
 -int plugin_register_complex_read (const char *name,
 +int plugin_register_complex_read (const char *group, const char *name,
                plugin_read_cb callback,
                const struct timespec *interval,
                user_data_t *user_data)
  
        memset (rf, 0, sizeof (read_func_t));
        rf->rf_callback = (void *) callback;
 +      if (group != NULL)
 +              sstrncpy (rf->rf_group, group, sizeof (rf->rf_group));
 +      else
 +              rf->rf_group[0] = '\0';
        sstrncpy (rf->rf_name, name, sizeof (rf->rf_name));
        rf->rf_type = RF_COMPLEX;
        if (interval != NULL)
@@@ -957,67 -961,6 +967,67 @@@ int plugin_unregister_read (const char 
        return (0);
  } /* }}} int plugin_unregister_read */
  
 +static int compare_read_func_group (llentry_t *e, void *ud) /* {{{ */
 +{
 +      read_func_t *rf    = e->value;
 +      char        *group = ud;
 +
 +      return strcmp (rf->rf_group, (const char *)group);
 +} /* }}} int compare_read_func_group */
 +
 +int plugin_unregister_read_group (const char *group) /* {{{ */
 +{
 +      llentry_t *le;
 +      read_func_t *rf;
 +
 +      int found = 0;
 +
 +      if (group == NULL)
 +              return (-ENOENT);
 +
 +      pthread_mutex_lock (&read_lock);
 +
 +      if (read_list == NULL)
 +      {
 +              pthread_mutex_unlock (&read_lock);
 +              return (-ENOENT);
 +      }
 +
 +      while (42)
 +      {
 +              le = llist_search_custom (read_list,
 +                              compare_read_func_group, (void *)group);
 +
 +              if (le == NULL)
 +                      break;
 +
 +              ++found;
 +
 +              llist_remove (read_list, le);
 +
 +              rf = le->value;
 +              assert (rf != NULL);
 +              rf->rf_type = RF_REMOVE;
 +
 +              llentry_destroy (le);
 +
 +              DEBUG ("plugin_unregister_read_group: "
 +                              "Marked `%s' (group `%s') for removal.",
 +                              rf->rf_name, group);
 +      }
 +
 +      pthread_mutex_unlock (&read_lock);
 +
 +      if (found == 0)
 +      {
 +              WARNING ("plugin_unregister_read_group: No such "
 +                              "group of read function: %s", group);
 +              return (-ENOENT);
 +      }
 +
 +      return (0);
 +} /* }}} int plugin_unregister_read_group */
 +
  int plugin_unregister_write (const char *name)
  {
        return (plugin_unregister (list_write, name));
diff --combined src/processes.c
@@@ -6,7 -6,6 +6,7 @@@
   * Copyright (C) 2009       Sebastian Harl
   * Copyright (C) 2009       Andrés J. Díaz
   * Copyright (C) 2009       Manuel Sanmartin
 + * Copyright (C) 2010       Clément Stenac
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License as published by the
@@@ -29,7 -28,6 +29,7 @@@
   *   Sebastian Harl <sh at tokkee.org>
   *   Andrés J. Díaz <ajdiaz at connectical.com>
   *   Manuel Sanmartin
 + *   Clément Stenac <clement.stenac at diwi.org>
   **/
  
  #include "collectd.h"
  #  define ARG_MAX 4096
  #endif
  
 -#define BUFSIZE 256
 -
  static const char *config_keys[] =
  {
        "Process",
@@@ -137,8 -137,6 +137,8 @@@ typedef struct procstat_entry_
        unsigned long num_lwp;
        unsigned long vmem_size;
        unsigned long vmem_rss;
 +      unsigned long vmem_data;
 +      unsigned long vmem_code;
        unsigned long stack_size;
  
        unsigned long vmem_minflt;
@@@ -172,8 -170,6 +172,8 @@@ typedef struct procsta
        unsigned long num_lwp;
        unsigned long vmem_size;
        unsigned long vmem_rss;
 +      unsigned long vmem_data;
 +      unsigned long vmem_code;
        unsigned long stack_size;
  
        unsigned long vmem_minflt_counter;
@@@ -368,8 -364,6 +368,8 @@@ static void ps_list_add (const char *na
                pse->num_lwp    = entry->num_lwp;
                pse->vmem_size  = entry->vmem_size;
                pse->vmem_rss   = entry->vmem_rss;
 +              pse->vmem_data  = entry->vmem_data;
 +              pse->vmem_code  = entry->vmem_code;
                pse->stack_size = entry->stack_size;
                pse->io_rchar   = entry->io_rchar;
                pse->io_wchar   = entry->io_wchar;
                ps->num_lwp    += pse->num_lwp;
                ps->vmem_size  += pse->vmem_size;
                ps->vmem_rss   += pse->vmem_rss;
 +              ps->vmem_data  += pse->vmem_data;
 +              ps->vmem_code  += pse->vmem_code;
                ps->stack_size += pse->stack_size;
  
                ps->io_rchar   += ((pse->io_rchar == -1)?0:pse->io_rchar);
@@@ -478,8 -470,6 +478,8 @@@ static void ps_list_reset (void
                ps->num_lwp     = 0;
                ps->vmem_size   = 0;
                ps->vmem_rss    = 0;
 +              ps->vmem_data   = 0;
 +              ps->vmem_code   = 0;
                ps->stack_size  = 0;
                ps->io_rchar = -1;
                ps->io_wchar = -1;
@@@ -648,16 -638,6 +648,16 @@@ static void ps_submit_proc_list (procst
        vl.values_len = 1;
        plugin_dispatch_values (&vl);
  
 +      sstrncpy (vl.type, "ps_data", sizeof (vl.type));
 +      vl.values[0].gauge = ps->vmem_data;
 +      vl.values_len = 1;
 +      plugin_dispatch_values (&vl);
 +
 +      sstrncpy (vl.type, "ps_code", sizeof (vl.type));
 +      vl.values[0].gauge = ps->vmem_code;
 +      vl.values_len = 1;
 +      plugin_dispatch_values (&vl);
 +
        sstrncpy (vl.type, "ps_stacksize", sizeof (vl.type));
        vl.values[0].gauge = ps->stack_size;
        vl.values_len = 1;
                plugin_dispatch_values (&vl);
        }
  
 -      DEBUG ("name = %s; num_proc = %lu; num_lwp = %lu; vmem_rss = %lu; "
 +      DEBUG ("name = %s; num_proc = %lu; num_lwp = %lu; "
 +                        "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; "
 +                      "vmem_code = %lu; "
                        "vmem_minflt_counter = %lu; vmem_majflt_counter = %lu; "
                        "cpu_user_counter = %lu; cpu_system_counter = %lu; "
                        "io_rchar = %"PRIi64"; io_wchar = %"PRIi64"; "
                        "io_syscr = %"PRIi64"; io_syscw = %"PRIi64";",
 -                      ps->name, ps->num_proc, ps->num_lwp, ps->vmem_rss,
 +                      ps->name, ps->num_proc, ps->num_lwp,
 +                      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);
@@@ -743,69 -719,6 +743,69 @@@ static int ps_read_tasks (int pid
        return ((count >= 1) ? count : 1);
  } /* int *ps_read_tasks */
  
 +/* Read advanced virtual memory data from /proc/pid/status */
 +static procstat_t *ps_read_vmem (int pid, procstat_t *ps)
 +{
 +      FILE *fh;
 +      char buffer[1024];
 +      char filename[64];
 +      unsigned long long lib = 0;
 +      unsigned long long exe = 0;
 +      unsigned long long data = 0;
 +      char *fields[8];
 +      int numfields;
 +
 +      ssnprintf (filename, sizeof (filename), "/proc/%i/status", pid);
 +      if ((fh = fopen (filename, "r")) == NULL)
 +              return (NULL);
 +
 +      while (fgets (buffer, sizeof(buffer), fh) != NULL)
 +      {
 +              long long tmp;
 +              char *endptr;
 +
 +              if (strncmp (buffer, "Vm", 2) != 0)
 +                      continue;
 +
 +              numfields = strsplit (buffer, fields,
 +                                      STATIC_ARRAY_SIZE (fields));
 +
 +              if (numfields < 2)
 +                      continue;
 +
 +              errno = 0;
 +              endptr = NULL;
 +              tmp = strtoll (fields[1], &endptr, /* base = */ 10);
 +              if ((errno == 0) && (endptr != fields[1]))
 +              {
 +                      if (strncmp (buffer, "VmData", 6) == 0) 
 +                      {
 +                              data = tmp;
 +                      }
 +                      else if (strncmp (buffer, "VmLib", 5) == 0)
 +                      {
 +                              lib = tmp;
 +                      }
 +                      else if  (strncmp(buffer, "VmExe", 5) == 0)
 +                      {
 +                              exe = tmp;
 +                      }
 +              }
 +      } /* while (fgets) */
 +
 +      if (fclose (fh))
 +      {
 +              char errbuf[1024];
 +              WARNING ("processes: fclose: %s",
 +                              sstrerror (errno, errbuf, sizeof (errbuf)));
 +      }
 +
 +      ps->vmem_data = data * 1024;
 +      ps->vmem_code = (exe + lib) * 1024;
 +
 +      return (ps);
 +} /* procstat_t *ps_read_vmem */
 +
  static procstat_t *ps_read_io (int pid, procstat_t *ps)
  {
        FILE *fh;
        if ((fh = fopen (filename, "r")) == NULL)
                return (NULL);
  
 -      while (fgets (buffer, 1024, fh) != NULL)
 +      while (fgets (buffer, sizeof (buffer), fh) != NULL)
        {
                derive_t *val = NULL;
                long long tmp;
                else
                        continue;
  
 -              numfields = strsplit (buffer, fields, 8);
 +              numfields = strsplit (buffer, fields,
 +                              STATIC_ARRAY_SIZE (fields));
  
                if (numfields < 2)
                        continue;
@@@ -889,7 -801,7 +889,7 @@@ int ps_read_process (int pid, procstat_
                return (-1);
        buffer[i] = 0;
  
 -      fields_len = strsplit (buffer, fields, 64);
 +      fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
        if (fields_len < 24)
        {
                DEBUG ("processes plugin: ps_read_process (pid = %i):"
        cpu_system_counter = cpu_system_counter * 1000000 / CONFIG_HZ;
        vmem_rss = vmem_rss * pagesize_g;
  
 +      if ( (ps_read_vmem(pid, ps)) == NULL)
 +      {
 +              /* No VMem data */
 +              ps->vmem_data = -1;
 +              ps->vmem_code = -1;
 +              DEBUG("ps_read_process: did not get vmem data for pid %i",pid);
 +      }
 +
        ps->cpu_user_counter = (unsigned long) cpu_user_counter;
        ps->cpu_system_counter = (unsigned long) cpu_system_counter;
        ps->vmem_size = (unsigned long) vmem_size;
@@@ -1110,7 -1014,7 +1110,7 @@@ static unsigned long read_fork_rate (
  
                errno = 0;
                endptr = NULL;
 -              result = strtoul(fields[1], &endptr, 10);
 +              result = strtoul(fields[1], &endptr, /* base = */ 10);
                if ((endptr == fields[1]) || (errno != 0)) {
                        ERROR ("processes plugin: Cannot parse fork rate: %s",
                                        fields[1]);
@@@ -1309,11 -1213,7 +1309,11 @@@ static int ps_read (void
                                }
  
                                pse.num_proc++;
 +                              pse.vmem_size = task_basic_info.virtual_size;
                                pse.vmem_rss = task_basic_info.resident_size;
 +                              /* Does not seem to be easily exposed */
 +                              pse.vmem_data = 0;
 +                              pse.vmem_code = 0;
  
                                pse.vmem_minflt_counter = task_events_info.cow_faults;
                                pse.vmem_majflt_counter = task_events_info.faults;
                pse.num_lwp    = ps.num_lwp;
                pse.vmem_size  = ps.vmem_size;
                pse.vmem_rss   = ps.vmem_rss;
 +              pse.vmem_data  = ps.vmem_data;
 +              pse.vmem_code  = ps.vmem_code;
                pse.stack_size = ps.stack_size;
  
                pse.vmem_minflt = 0;
        procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &count);
        if (procs == NULL)
        {
-               kvm_close (kd);
                ERROR ("processes plugin: Cannot get kvm processes list: %s",
                                kvm_geterr(kd));
+               kvm_close (kd);
                return (0);
        }
  
  
                pse.vmem_size = procs[i].ki_size;
                pse.vmem_rss = procs[i].ki_rssize * getpagesize();
 +              pse.vmem_data = procs[i].ki_dsize * getpagesize();
 +              pse.vmem_code = procs[i].ki_tsize * getpagesize();
                pse.stack_size = procs[i].ki_ssize * getpagesize();
                pse.vmem_minflt = 0;
                pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt;
  
                        pse.vmem_size = procentry[i].pi_tsize + procentry[i].pi_dvm * pagesize;
                        pse.vmem_rss = (procentry[i].pi_drss + procentry[i].pi_trss) * pagesize;
 +                      /* Not supported */
 +                      pse.vmem_data = 0;
 +                      pse.vmem_code = 0;
                        pse.stack_size =  0;
  
                        pse.io_rchar = -1;
diff --combined src/python.c
@@@ -245,7 -245,7 +245,7 @@@ static void cpy_build_name(char *buf, s
        
        mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
        if (mod != NULL)
 -              module = PyString_AsString(mod);
 +              module = cpy_unicode_or_bytes_to_string(&mod);
        
        if (module != NULL) {
                snprintf(buf, size, "python.%s", module);
        PyErr_Clear();
  }
  
 -static void cpy_log_exception(const char *context) {
 +void cpy_log_exception(const char *context) {
        int l = 0, i;
        const char *typename = NULL, *message = NULL;
        PyObject *type, *value, *traceback, *tn, *m, *list;
        PyErr_NormalizeException(&type, &value, &traceback);
        if (type == NULL) return;
        tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
 -      m = PyObject_GetAttrString(value, "message"); /* New reference. */
 +      m = PyObject_Str(value); /* New reference. */
        if (tn != NULL)
 -              typename = PyString_AsString(tn);
 +              typename = cpy_unicode_or_bytes_to_string(&tn);
        if (m != NULL)
 -              message = PyString_AsString(m);
 +              message = cpy_unicode_or_bytes_to_string(&m);
        if (typename == NULL)
                typename = "NamelessException";
        if (message == NULL)
                PyObject *line;
                
                line = PyList_GET_ITEM(list, i); /* Borrowed reference. */
 -              s = strdup(PyString_AsString(line));
 +              Py_INCREF(line);
 +              s = strdup(cpy_unicode_or_bytes_to_string(&line));
 +              Py_DECREF(line);
                if (s[strlen(s) - 1] == '\n')
                        s[strlen(s) - 1] = 0;
                Py_BEGIN_ALLOW_THREADS
@@@ -335,8 -333,7 +335,8 @@@ static int cpy_read_callback(user_data_
  static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_list, user_data_t *data) {
        int i;
        cpy_callback_t *c = data->data;
 -      PyObject *ret, *v, *list;
 +      PyObject *ret, *list, *temp, *dict = NULL, *val;
 +      Values *v;
  
        CPY_LOCK_THREADS
                list = PyList_New(value_list->values_len); /* New reference. */
                        CPY_RETURN_FROM_THREADS 0;
                }
                for (i = 0; i < value_list->values_len; ++i) {
-                       if (ds->ds->type == DS_TYPE_COUNTER) {
+                       if (ds->ds[i].type == DS_TYPE_COUNTER) {
                                if ((long) value_list->values[i].counter == value_list->values[i].counter)
                                        PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].counter));
                                else
                                        PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].counter));
-                       } else if (ds->ds->type == DS_TYPE_GAUGE) {
+                       } else if (ds->ds[i].type == DS_TYPE_GAUGE) {
                                PyList_SetItem(list, i, PyFloat_FromDouble(value_list->values[i].gauge));
-                       } else if (ds->ds->type == DS_TYPE_DERIVE) {
+                       } else if (ds->ds[i].type == DS_TYPE_DERIVE) {
                                if ((long) value_list->values[i].derive == value_list->values[i].derive)
                                        PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].derive));
                                else
                                        PyList_SetItem(list, i, PyLong_FromLongLong(value_list->values[i].derive));
-                       } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
+                       } else if (ds->ds[i].type == DS_TYPE_ABSOLUTE) {
                                if ((long) value_list->values[i].absolute == value_list->values[i].absolute)
                                        PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].absolute));
                                else
                                        PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
                        } else {
                                Py_BEGIN_ALLOW_THREADS
-                               ERROR("cpy_write_callback: Unknown value type %d.", ds->ds->type);
+                               ERROR("cpy_write_callback: Unknown value type %d.", ds->ds[i].type);
                                Py_END_ALLOW_THREADS
                                Py_DECREF(list);
                                CPY_RETURN_FROM_THREADS 0;
                                CPY_RETURN_FROM_THREADS 0;
                        }
                }
 -              v = PyObject_CallFunction((void *) &ValuesType, "sOssssdi", value_list->type,
 -                              list, value_list->plugin_instance, value_list->type_instance,
 -                              value_list->plugin, value_list->host, (double) value_list->time,
 -                              value_list->interval); /* New reference. */
 -              Py_DECREF(list);
 +              dict = PyDict_New();
 +              if (value_list->meta) {
 +                      int i, num;
 +                      char **table;
 +                      meta_data_t *meta = value_list->meta;
 +
 +                      num = meta_data_toc(meta, &table);
 +                      for (i = 0; i < num; ++i) {
 +                              int type;
 +                              char *string;
 +                              int64_t si;
 +                              uint64_t ui;
 +                              double d;
 +                              _Bool b;
 +                              
 +                              type = meta_data_type(meta, table[i]);
 +                              if (type == MD_TYPE_STRING) {
 +                                      if (meta_data_get_string(meta, table[i], &string))
 +                                              continue;
 +                                      temp = cpy_string_to_unicode_or_bytes(string);
 +                                      free(string);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_SIGNED_INT) {
 +                                      if (meta_data_get_signed_int(meta, table[i], &si))
 +                                              continue;
 +                                      temp = PyObject_CallFunctionObjArgs((void *) &SignedType, PyLong_FromLongLong(si), (void *) 0);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_UNSIGNED_INT) {
 +                                      if (meta_data_get_unsigned_int(meta, table[i], &ui))
 +                                              continue;
 +                                      temp = PyObject_CallFunctionObjArgs((void *) &UnsignedType, PyLong_FromUnsignedLongLong(ui), (void *) 0);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_DOUBLE) {
 +                                      if (meta_data_get_double(meta, table[i], &d))
 +                                              continue;
 +                                      temp = PyFloat_FromDouble(d);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_BOOLEAN) {
 +                                      if (meta_data_get_boolean(meta, table[i], &b))
 +                                              continue;
 +                                      if (b)
 +                                              PyDict_SetItemString(dict, table[i], Py_True);
 +                                      else
 +                                              PyDict_SetItemString(dict, table[i], Py_False);
 +                              }
 +                              free(table[i]);
 +                      }
 +                      free(table);
 +              }
 +              val = Values_New(); /* New reference. */
 +              v = (Values *) val; 
 +              sstrncpy(v->data.host, value_list->host, sizeof(v->data.host));
 +              sstrncpy(v->data.type, value_list->type, sizeof(v->data.type));
 +              sstrncpy(v->data.type_instance, value_list->type_instance, sizeof(v->data.type_instance));
 +              sstrncpy(v->data.plugin, value_list->plugin, sizeof(v->data.plugin));
 +              sstrncpy(v->data.plugin_instance, value_list->plugin_instance, sizeof(v->data.plugin_instance));
 +              v->data.time = value_list->time;
 +              v->interval = value_list->interval;
 +              Py_CLEAR(v->values);
 +              v->values = list;
 +              Py_CLEAR(v->meta);
 +              v->meta = dict;
                ret = PyObject_CallFunctionObjArgs(c->callback, v, c->data, (void *) 0); /* New reference. */
 -              Py_XDECREF(v);
 +              Py_XDECREF(val);
                if (ret == NULL) {
                        cpy_log_exception("write callback");
                } else {
  
  static int cpy_notification_callback(const notification_t *notification, user_data_t *data) {
        cpy_callback_t *c = data->data;
 -      PyObject *ret, *n;
 +      PyObject *ret, *notify;
 +      Notification *n;
  
        CPY_LOCK_THREADS
 -              n = PyObject_CallFunction((void *) &NotificationType, "ssssssdi", notification->type, notification->message,
 -                              notification->plugin_instance, notification->type_instance, notification->plugin,
 -                              notification->host, (double) notification->time, notification->severity); /* New reference. */
 +              notify = Notification_New(); /* New reference. */
 +              n = (Notification *) notify;
 +              sstrncpy(n->data.host, notification->host, sizeof(n->data.host));
 +              sstrncpy(n->data.type, notification->type, sizeof(n->data.type));
 +              sstrncpy(n->data.type_instance, notification->type_instance, sizeof(n->data.type_instance));
 +              sstrncpy(n->data.plugin, notification->plugin, sizeof(n->data.plugin));
 +              sstrncpy(n->data.plugin_instance, notification->plugin_instance, sizeof(n->data.plugin_instance));
 +              n->data.time = notification->time;
 +              sstrncpy(n->message, notification->message, sizeof(n->message));
 +              n->severity = notification->severity;
                ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data, (void *) 0); /* New reference. */
 -              Py_XDECREF(n);
 +              Py_XDECREF(notify);
                if (ret == NULL) {
                        cpy_log_exception("notification callback");
                } else {
  
  static void cpy_log_callback(int severity, const char *message, user_data_t *data) {
        cpy_callback_t * c = data->data;
 -      PyObject *ret;
 +      PyObject *ret, *text;
  
        CPY_LOCK_THREADS
 +      text = cpy_string_to_unicode_or_bytes(message);
        if (c->data == NULL)
 -              ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iN", severity, text); /* New reference. */
        else
 -              ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iNO", severity, text, c->data); /* New reference. */
  
        if (ret == NULL) {
                /* FIXME */
  
  static void cpy_flush_callback(int timeout, const char *id, user_data_t *data) {
        cpy_callback_t * c = data->data;
 -      PyObject *ret;
 +      PyObject *ret, *text;
  
        CPY_LOCK_THREADS
 +      text = cpy_string_to_unicode_or_bytes(id);
        if (c->data == NULL)
 -              ret = PyObject_CallFunction(c->callback, "is", timeout, id); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iN", timeout, text); /* New reference. */
        else
 -              ret = PyObject_CallFunction(c->callback, "isO", timeout, id, c->data); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iNO", timeout, text, c->data); /* New reference. */
  
        if (ret == NULL) {
                cpy_log_exception("flush callback");
@@@ -529,7 -455,7 +529,7 @@@ static PyObject *cpy_register_generic(c
        PyObject *callback = NULL, *data = NULL, *mod = NULL;
        static char *kwlist[] = {"callback", "data", "name", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL;
        if (PyCallable_Check(callback) == 0) {
                PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
                return NULL;
        c->next = *list_head;
        *list_head = c;
        Py_XDECREF(mod);
 -      return PyString_FromString(buf);
 +      return cpy_string_to_unicode_or_bytes(buf);
  }
  
  static PyObject *cpy_flush(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
        const char *plugin = NULL, *identifier = NULL;
        static char *kwlist[] = {"plugin", "timeout", "identifier", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "|ziz", kwlist, &plugin, &timeout, &identifier) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "|etiet", kwlist, NULL, &plugin, &timeout, NULL, &identifier) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_flush(plugin, timeout, identifier);
        Py_END_ALLOW_THREADS
@@@ -579,7 -505,7 +579,7 @@@ static PyObject *cpy_register_generic_u
        PyObject *callback = NULL, *data = NULL;
        static char *kwlist[] = {"callback", "data", "name", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL;
        if (PyCallable_Check(callback) == 0) {
                PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
                return NULL;
        user_data->free_func = cpy_destroy_user_data;
        user_data->data = c;
        register_function(buf, handler, user_data);
 -      return PyString_FromString(buf);
 +      return cpy_string_to_unicode_or_bytes(buf);
  }
  
  static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwds) {
        struct timespec ts;
        static char *kwlist[] = {"callback", "interval", "data", "name", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOz", kwlist, &callback, &interval, &data, &name) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOet", kwlist, &callback, &interval, &data, NULL, &name) == 0) return NULL;
        if (PyCallable_Check(callback) == 0) {
                PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
                return NULL;
        user_data->data = c;
        ts.tv_sec = interval;
        ts.tv_nsec = (interval - ts.tv_sec) * 1000000000;
 -      plugin_register_complex_read(buf, cpy_read_callback, &ts, user_data);
 -      return PyString_FromString(buf);
 +      plugin_register_complex_read(/* group = */ NULL, buf,
 +                      cpy_read_callback, &ts, user_data);
 +      return cpy_string_to_unicode_or_bytes(buf);
  }
  
  static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
@@@ -660,7 -585,7 +660,7 @@@ static PyObject *cpy_register_shutdown(
  
  static PyObject *cpy_error(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_ERR, "%s", text);
        Py_END_ALLOW_THREADS
  
  static PyObject *cpy_warning(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_WARNING, "%s", text);
        Py_END_ALLOW_THREADS
  
  static PyObject *cpy_notice(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_NOTICE, "%s", text);
        Py_END_ALLOW_THREADS
  
  static PyObject *cpy_info(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_INFO, "%s", text);
        Py_END_ALLOW_THREADS
  static PyObject *cpy_debug(PyObject *self, PyObject *args) {
  #ifdef COLLECT_DEBUG
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_DEBUG, "%s", text);
        Py_END_ALLOW_THREADS
@@@ -710,13 -635,17 +710,13 @@@ static PyObject *cpy_unregister_generic
        const char *name;
        cpy_callback_t *prev = NULL, *tmp;
  
 -      if (PyUnicode_Check(arg)) {
 -              arg = PyUnicode_AsEncodedString(arg, NULL, NULL);
 -              if (arg == NULL)
 -                      return NULL;
 -              name = PyString_AsString(arg);
 -              Py_DECREF(arg);
 -      } else if (PyString_Check(arg)) {
 -              name = PyString_AsString(arg);
 -      } else {
 +      Py_INCREF(arg);
 +      name = cpy_unicode_or_bytes_to_string(&arg);
 +      if (name == NULL) {
 +              PyErr_Clear();
                if (!PyCallable_Check(arg)) {
                        PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
 +                      Py_DECREF(arg);
                        return NULL;
                }
                cpy_build_name(buf, sizeof(buf), arg, NULL);
                if (strcmp(name, tmp->name) == 0)
                        break;
        
 +      Py_DECREF(arg);
        if (tmp == NULL) {
                PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
                return NULL;
@@@ -747,24 -675,25 +747,24 @@@ static PyObject *cpy_unregister_generic
        char buf[512];
        const char *name;
  
 -      if (PyUnicode_Check(arg)) {
 -              arg = PyUnicode_AsEncodedString(arg, NULL, NULL);
 -              if (arg == NULL)
 -                      return NULL;
 -              name = PyString_AsString(arg);
 -              Py_DECREF(arg);
 -      } else if (PyString_Check(arg)) {
 -              name = PyString_AsString(arg);
 -      } else {
 +      Py_INCREF(arg);
 +      name = cpy_unicode_or_bytes_to_string(&arg);
 +      if (name == NULL) {
 +              PyErr_Clear();
                if (!PyCallable_Check(arg)) {
                        PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
 +                      Py_DECREF(arg);
                        return NULL;
                }
                cpy_build_name(buf, sizeof(buf), arg, NULL);
                name = buf;
        }
 -      if (unreg(name) == 0)
 +      if (unreg(name) == 0) {
 +              Py_DECREF(arg);
                Py_RETURN_NONE;
 +      }
        PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
 +      Py_DECREF(arg);
        return NULL;
  }
  
@@@ -940,7 -869,7 +940,7 @@@ static PyObject *cpy_oconfig_to_pyconfi
        values = PyTuple_New(ci->values_num); /* New reference. */
        for (i = 0; i < ci->values_num; ++i) {
                if (ci->values[i].type == OCONFIG_TYPE_STRING) {
 -                      PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
 +                      PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes(ci->values[i].value.string));
                } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
                        PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
                } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
                }
        }
        
 -      item = PyObject_CallFunction((void *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
 +      tmp = cpy_string_to_unicode_or_bytes(ci->key);
 +      item = PyObject_CallFunction((void *) &ConfigType, "NONO", tmp, parent, values, Py_None);
        if (item == NULL)
                return NULL;
        children = PyTuple_New(ci->children_num); /* New reference. */
        return item;
  }
  
 +#ifdef IS_PY3K
 +static struct PyModuleDef collectdmodule = {
 +      PyModuleDef_HEAD_INIT,
 +      "collectd",   /* name of module */
 +      "The python interface to collectd", /* module documentation, may be NULL */
 +      -1,
 +      cpy_methods
 +};
 +
 +PyMODINIT_FUNC PyInit_collectd(void) {
 +      return PyModule_Create(&collectdmodule);
 +}
 +#endif
 +
  static int cpy_config(oconfig_item_t *ci) {
        int i;
        char *argv = "";
         * python code during the config callback so we have to start
         * the interpreter here. */
        /* Do *not* use the python "thread" module at this point! */
 +
 +#ifdef IS_PY3K
 +      /* Add a builtin module, before Py_Initialize */
 +      PyImport_AppendInittab("collectd", PyInit_collectd);
 +#endif
 +      
        Py_Initialize();
        
        PyType_Ready(&ConfigType);
        PyType_Ready(&ValuesType);
        NotificationType.tp_base = &PluginDataType;
        PyType_Ready(&NotificationType);
 +      SignedType.tp_base = &PyLong_Type;
 +      PyType_Ready(&SignedType);
 +      UnsignedType.tp_base = &PyLong_Type;
 +      PyType_Ready(&UnsignedType);
        sys = PyImport_ImportModule("sys"); /* New reference. */
        if (sys == NULL) {
                cpy_log_exception("python initialization");
        PySys_SetArgv(1, &argv);
        PyList_SetSlice(sys_path, 0, 1, NULL);
  
 +#ifdef IS_PY3K
 +      module = PyImport_ImportModule("collectd");
 +#else
        module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
 +#endif
        PyModule_AddObject(module, "Config", (void *) &ConfigType); /* Steals a reference. */
        PyModule_AddObject(module, "Values", (void *) &ValuesType); /* Steals a reference. */
        PyModule_AddObject(module, "Notification", (void *) &NotificationType); /* Steals a reference. */
 +      PyModule_AddObject(module, "Signed", (void *) &SignedType); /* Steals a reference. */
 +      PyModule_AddObject(module, "Unsigned", (void *) &UnsignedType); /* Steals a reference. */
        PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
        PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
        PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
                        
                        if (cf_util_get_string(item, &dir) != 0) 
                                continue;
 -                      dir_object = PyString_FromString(dir); /* New reference. */
 +                      dir_object = cpy_string_to_unicode_or_bytes(dir); /* New reference. */
                        if (dir_object == NULL) {
                                ERROR("python plugin: Unable to convert \"%s\" to "
                                      "a python object.", dir);
                        if (module == NULL) {
                                ERROR("python plugin: Error importing module \"%s\".", module_name);
                                cpy_log_exception("importing module");
 -                              PyErr_Print();
                        }
                        free(module_name);
                        Py_XDECREF(module);