Merge branch 'collectd-5.6' into collectd-5.7
authorFlorian Forster <octo@collectd.org>
Tue, 16 May 2017 17:34:25 +0000 (19:34 +0200)
committerFlorian Forster <octo@collectd.org>
Tue, 16 May 2017 17:34:25 +0000 (19:34 +0200)
1  2 
src/collectd.conf.pod
src/curl_json.c
src/perl.c

diff --combined src/collectd.conf.pod
@@@ -555,7 -555,6 +555,7 @@@ B<Synopsis:
   #   GraphiteEscapeChar "_"
   #   GraphiteSeparateInstances false
   #   GraphiteAlwaysAppendDS false
 + #   GraphitePreserveSeparator false
     </Publish>
  
     # Receive values from an AMQP broker
@@@ -730,12 -729,6 +730,12 @@@ If set to B<true>, append the name of t
  identifier. If set to B<false> (the default), this is only done when there is
  more than one DS.
  
 +=item B<GraphitePreserveSeparator> B<false>|B<true>
 +
 +If set to B<false> (the default) the C<.> (dot) character is replaced with
 +I<GraphiteEscapeChar>. Otherwise, if set to B<true>, the C<.> (dot) character
 +is preserved, i.e. passed through.
 +
  =back
  
  =head2 Plugin C<apache>
@@@ -1121,13 -1114,6 +1121,13 @@@ When set to B<true>, the battery plugi
  and "remaining capacity") and B<degraded> (difference between "design capacity"
  and "last full capacity").
  
 +=item B<QueryStateFS> B<false>|B<true>
 +
 +When set to B<true>, the battery plugin will only read statistics
 +related to battery performance as exposed by StateFS at
 +/run/state. StateFS is used in Mer-based Sailfish OS, for
 +example.
 +
  =back
  
  =head2 Plugin C<bind>
@@@ -2393,67 -2379,6 +2393,67 @@@ Enabled by default, collects unknown (a
  
  =back
  
 +=head2 Plugin C<dpdkstat>
 +
 +The I<dpdkstat plugin> collects information about DPDK interfaces using the
 +extended NIC stats API in DPDK.
 +
 +B<Synopsis:>
 +
 + <Plugin "dpdkstat">
 +    Coremask "0x4"
 +    MemoryChannels "4"
 +    ProcessType "secondary"
 +    FilePrefix "rte"
 +    EnabledPortMask 0xffff
 +    PortName "interface1"
 +    PortName "interface2"
 + </Plugin>
 +
 +B<Options:>
 +
 +=over 4
 +
 +=item B<Coremask> I<Mask>
 +
 +A string containing an hexadecimal bit mask of the cores to run on. Note that
 +core numbering can change between platforms and should be determined beforehand.
 +
 +=item B<Memorychannels> I<Channels>
 +
 +A string containing a number of memory channels per processor socket.
 +
 +=item B<ProcessType> I<type>
 +
 +A string containing the type of DPDK process instance.
 +
 +=item B<FilePrefix> I<File>
 +
 +The prefix text used for hugepage filenames. The filename will be set to
 +/var/run/.<prefix>_config where prefix is what is passed in by the user.
 +
 +=item B<SocketMemory> I<MB>
 +
 +A string containing amount of Memory to allocate from hugepages on specific
 +sockets in MB
 +
 +=item B<EnabledPortMask> I<Mask>
 +
 +A hexidecimal bit mask of the DPDK ports which should be enabled. A mask
 +of 0x0 means that all ports will be disabled. A bitmask of all Fs means
 +that all ports will be enabled. This is an optional argument - default
 +is all ports enabled.
 +
 +=item B<PortName> I<Name>
 +
 +A string containing an optional name for the enabled DPDK ports. Each PortName
 +option should contain only one port name; specify as many PortName options as
 +desired. Default naming convention will be used if PortName is blank. If there
 +are less PortName options than there are enabled ports, the default naming
 +convention will be used for the additional ports.
 +
 +=back
 +
  =head2 Plugin C<email>
  
  =over 4
@@@ -2904,101 -2829,6 +2904,101 @@@ TCP-Port to connect to. Defaults to B<7
  
  =back
  
 +=head2 Plugin C<hugepages>
 +
 +To collect B<hugepages> information, collectd reads directories
 +"/sys/devices/system/node/*/hugepages" and
 +"/sys/kernel/mm/hugepages".
 +Reading of these directories can be disabled by the following
 +options (default is enabled).
 +
 +=over 4
 +
 +=item B<ReportPerNodeHP> B<true>|B<false>
 +
 +If enabled, information will be collected from the hugepage
 +counters in "/sys/devices/system/node/*/hugepages".
 +This is used to check the per-node hugepage statistics on
 +a NUMA system.
 +
 +=item B<ReportRootHP> B<true>|B<false>
 +
 +If enabled, information will be collected from the hugepage
 +counters in "/sys/kernel/mm/hugepages".
 +This can be used on both NUMA and non-NUMA systems to check
 +the overall hugepage statistics.
 +
 +=item B<ValuesPages> B<true>|B<false>
 +
 +Whether to report hugepages metrics in number of pages.
 +Defaults to B<true>.
 +
 +=item B<ValuesBytes> B<false>|B<true>
 +
 +Whether to report hugepages metrics in bytes.
 +Defaults to B<false>.
 +
 +=item B<ValuesPercentage> B<false>|B<true>
 +
 +Whether to report hugepages metrics as percentage.
 +Defaults to B<false>.
 +
 +=back
 +
 +=head2 Plugin C<intel_rdt>
 +
 +The I<intel_rdt> plugin collects information provided by monitoring features of
 +Intel Resource Director Technology (Intel(R) RDT) like Cache Monitoring
 +Technology (CMT), Memory Bandwidth Monitoring (MBM). These features provide
 +information about utilization of shared resources. CMT monitors last level cache
 +occupancy (LLC). MBM supports two types of events reporting local and remote
 +memory bandwidth. Local memory bandwidth (MBL) reports the bandwidth of
 +accessing memory associated with the local socket. Remote memory bandwidth (MBR)
 +reports the bandwidth of accessing the remote socket. Also this technology
 +allows to monitor instructions per clock (IPC).
 +Monitor events are hardware dependant. Monitoring capabilities are detected on
 +plugin initialization and only supported events are monitored.
 +
 +B<Synopsis:>
 +
 +  <Plugin "intel_rdt">
 +    Cores "0-2" "3,4,6" "8-10,15"
 +  </Plugin>
 +
 +B<Options:>
 +
 +=over 4
 +
 +=item B<Interval> I<seconds>
 +
 +The interval within which to retrieve statistics on monitored events in seconds.
 +For milliseconds divide the time by 1000 for example if the desired interval
 +is 50ms, set interval to 0.05. Due to limited capacity of counters it is not
 +recommended to set interval higher than 1 sec.
 +
 +=item B<Cores> I<cores groups>
 +
 +All events are reported on a per core basis. Monitoring of the events can be
 +configured for group of cores (aggregated statistics). This field defines groups
 +of cores on which to monitor supported events. The field is represented as list
 +of strings with core group values. Each string represents a list of cores in a
 +group. Allowed formats are:
 +    0,1,2,3
 +    0-10,20-18
 +    1,3,5-8,10,0x10-12
 +
 +If an empty string is provided as value for this field default cores
 +configuration is applied - a separate group is created for each core.
 +
 +=back
 +
 +B<Note:> By default global interval is used to retrieve statistics on monitored
 +events. To configure a plugin specific interval use B<Interval> option of the
 +intel_rdt <LoadPlugin> block. For milliseconds divide the time by 1000 for
 +example if the desired interval is 50ms, set interval to 0.05.
 +Due to limited capacity of counters it is not recommended to set interval higher
 +than 1 sec.
 +
  =head2 Plugin C<interface>
  
  =over 4
@@@ -3420,12 -3250,11 +3420,12 @@@ interpreted. For a description of matc
  
  The B<memcached plugin> connects to a memcached server and queries statistics
  about cache utilization, memory and bandwidth used.
 -L<http://www.danga.com/memcached/>
 +L<http://memcached.org/>
  
   <Plugin "memcached">
     <Instance "name">
 -     Host "memcache.example.com"
 +     #Host "memcache.example.com"
 +     Address "127.0.0.1"
       Port 11211
     </Instance>
   </Plugin>
@@@ -3438,25 -3267,16 +3438,25 @@@ following options are allowed
  
  =item B<Host> I<Hostname>
  
 -Hostname to connect to. Defaults to B<127.0.0.1>.
 +Sets the B<host> field of dispatched values. Defaults to the global hostname
 +setting.
 +For backwards compatibility, values are also dispatched with the global
 +hostname when B<Host> is set to B<127.0.0.1> or B<localhost> and B<Address> is
 +not set.
 +
 +=item B<Address> I<Address>
 +
 +Hostname or IP to connect to. For backwards compatibility, defaults to the
 +value of B<Host> or B<127.0.0.1> if B<Host> is unset.
  
  =item B<Port> I<Port>
  
 -TCP-Port to connect to. Defaults to B<11211>.
 +TCP port to connect to. Defaults to B<11211>.
  
  =item B<Socket> I<Path>
  
  Connect to I<memcached> using the UNIX domain socket at I<Path>. If this
 -setting is given, the B<Host> and B<Port> settings are ignored.
 +setting is given, the B<Address> and B<Port> settings are ignored.
  
  =back
  
@@@ -5556,10 -5376,10 +5556,10 @@@ multiple hosts
  =item B<Interval> I<Seconds>
  
  Sets the interval in which to send ICMP echo packets to the configured hosts.
- This is B<not> the interval in which statistics are queries from the plugin but
the interval in which the hosts are "pinged". Therefore, the setting here
- should be smaller than or equal to the global B<Interval> setting. Fractional
times, such as "1.24" are allowed.
+ This is B<not> the interval in which metrics are read from the plugin but the
interval in which the hosts are "pinged". Therefore, the setting here should be
+ smaller than or equal to the global B<Interval> setting. Fractional times, such
+ as "1.24" are allowed.
  
  Default: B<1.0>
  
@@@ -5903,7 -5723,7 +5903,7 @@@ values are made available through thos
  
  =item B<$1>
  
 -The timestamp of the queried value as a floating point number.
 +The timestamp of the queried value as an RFC 3339-formatted local time.
  
  =item B<$2>
  
@@@ -7119,15 -6939,6 +7119,15 @@@ user using (extended) regular expressio
          Type "counter"
          Instance "local_user"
        </Match>
 +      <Match>
 +        Regex "l=([0-9]*\\.[0-9]*)"
 +        <DSType "Distribution">
 +          Percentile 99
 +          Bucket 0 100
 +        </DSType>
 +        Type "latency"
 +        Instance "foo"
 +      </Match>
      </File>
    </Plugin>
  
@@@ -7190,13 -7001,6 +7190,13 @@@ Use the greatest number only
  
  Use the last number found.
  
 +=item B<GaugePersist>
 +
 +Use the last number found. The number is not reset at the end of an interval.
 +It is continously reported until another number is matched. This is intended
 +for cases in which only state changes are reported, for example a thermometer
 +that only reports the temperature when it changes.
 +
  =item B<CounterSet>
  
  =item B<DeriveSet>
@@@ -7226,74 -7030,14 +7226,74 @@@ Increase the internal counter by one. T
  not use the matched subexpression, but simply count the number of matched
  lines. Thus, you may use a regular expression without submatch in this case.
  
 +=item B<Distribution>
 +
 +Type to do calculations based on the distribution of values, primarily
 +calculating percentiles. This is primarily geared towards latency, but can be
 +used for other metrics as well. The range of values tracked with this setting
 +must be in the range (0–2^34) and can be fractional. Please note that neither
 +zero nor 2^34 are inclusive bounds, i.e. zero I<cannot> be handled by a
 +distribution.
 +
 +This option must be used together with the B<Percentile> and/or B<Bucket>
 +options.
 +
 +B<Synopsis:>
 +
 +  <DSType "Distribution">
 +    Percentile 99
 +    Bucket 0 100
 +  </DSType>
 +
 +=over 4
 +
 +=item B<Percentile> I<Percent>
 +
 +Calculate and dispatch the configured percentile, i.e. compute the value, so
 +that I<Percent> of all matched values are smaller than or equal to the computed
 +latency.
 +
 +Metrics are reported with the I<type> B<Type> (the value of the above option)
 +and the I<type instance> C<[E<lt>InstanceE<gt>-]E<lt>PercentE<gt>>.
 +
 +This option may be repeated to calculate more than one percentile.
 +
 +=item B<Bucket> I<lower_bound> I<upper_bound>
 +
 +Export the number of values (a C<DERIVE>) falling within the given range. Both,
 +I<lower_bound> and I<upper_bound> may be a fractional number, such as B<0.5>.
 +Each B<Bucket> option specifies an interval C<(I<lower_bound>,
 +I<upper_bound>]>, i.e. the range I<excludes> the lower bound and I<includes>
 +the upper bound. I<lower_bound> and I<upper_bound> may be zero, meaning no
 +lower/upper bound.
 +
 +To export the entire (0–inf) range without overlap, use the upper bound of the
 +previous range as the lower bound of the following range. In other words, use
 +the following schema:
 +
 +  Bucket   0   1
 +  Bucket   1   2
 +  Bucket   2   5
 +  Bucket   5  10
 +  Bucket  10  20
 +  Bucket  20  50
 +  Bucket  50   0
 +
 +Metrics are reported with the I<type> C<bucket> and the I<type instance>
 +C<E<lt>TypeE<gt>[-E<lt>InstanceE<gt>]-E<lt>lower_boundE<gt>_E<lt>upper_boundE<gt>>.
 +
 +This option may be repeated to calculate more than one rate.
 +
  =back
  
 -As you'd expect the B<Gauge*> types interpret the submatch as a floating point
 -number, using L<strtod(3)>. The B<Counter*> and B<AbsoluteSet> types interpret
 -the submatch as an unsigned integer using L<strtoull(3)>. The B<Derive*> types
 -interpret the submatch as a signed integer using L<strtoll(3)>. B<CounterInc>
 -and B<DeriveInc> do not use the submatch at all and it may be omitted in this
 -case.
 +=back
 +
 +The B<Gauge*> and B<Distribution> types interpret the submatch as a floating
 +point number, using L<strtod(3)>. The B<Counter*> and B<AbsoluteSet> types
 +interpret the submatch as an unsigned integer using L<strtoull(3)>. The
 +B<Derive*> types interpret the submatch as a signed integer using
 +L<strtoll(3)>. B<CounterInc> and B<DeriveInc> do not use the submatch at all
 +and it may be omitted in this case.
  
  =item B<Type> I<Type>
  
@@@ -7550,7 -7294,7 +7550,7 @@@ couple metrics: number of records, and 
  
  =item B<Host> I<Hostname/IP>
  
 -The hostname or ip which identifies the server.
 +The hostname or IP which identifies the server.
  Default: B<127.0.0.1>
  
  =item B<Port> I<Service/Port>
@@@ -7564,60 -7308,61 +7564,60 @@@ Default: B<1978
  =head2 Plugin C<turbostat>
  
  The I<Turbostat plugin> reads CPU frequency and C-state residency on modern
 -Intel processors by using the new Model Specific Registers.
 +Intel processors by using I<Model Specific Registers>.
  
  =over 4
  
  =item B<CoreCstates> I<Bitmask(Integer)>
  
 -Bitmask of the list of core C states supported by the processor.
 +Bit mask of the list of core C-states supported by the processor.
  This option should only be used if the automated detection fails.
 -Default value extracted from the cpu model and family.
 +Default value extracted from the CPU model and family.
  
  Currently supported C-states (by this plugin): 3, 6, 7
  
 -Example: (1<<3)+(1<<6)+(1<<7) = 392 for all states
 +B<Example:>
 +
 +  All states (3, 6 and 7):
 +  (1<<3) + (1<<6) + (1<<7) = 392
  
  =item B<PackageCstates> I<Bitmask(Integer)>
  
 -Bitmask of the list of pacages C states supported by the processor.
 -This option should only be used if the automated detection fails.
 -Default value extracted from the cpu model and family.
 +Bit mask of the list of packages C-states supported by the processor. This
 +option should only be used if the automated detection fails. Default value
 +extracted from the CPU model and family.
  
  Currently supported C-states (by this plugin): 2, 3, 6, 7, 8, 9, 10
  
 -Example: (1<<2)+(1<<3)+(1<<6)+(1<<7) = 396 for states 2, 3, 6 and 7
 -
 -=item B<SystemManagementInterrupt> I<true>|I<false>
 +B<Example:>
  
 -Boolean enabling the collection of the I/O System-Management Interrupt
 -counter'. This option should only be used if the automated detection
 -fails or if you want to disable this feature.
 +  States 2, 3, 6 and 7:
 +  (1<<2) + (1<<3) + (1<<6) + (1<<7) = 396
  
 -=item B<DigitalTemperatureSensor> I<true>|I<false>
 +=item B<SystemManagementInterrupt> I<true>|I<false>
  
 -Boolean enabling the collection of the temperature of each core.
 -This option should only be used if the automated detectionfails or
 -if you want to disable this feature.
 +Boolean enabling the collection of the I/O System-Management Interrupt counter.
 +This option should only be used if the automated detection fails or if you want
 +to disable this feature.
  
  =item B<DigitalTemperatureSensor> I<true>|I<false>
  
 -Boolean enabling the collection of the temperature of each package.
 -This option should only be used if the automated detectionfails or
 -if you want to disable this feature.
 +Boolean enabling the collection of the temperature of each core. This option
 +should only be used if the automated detection fails or if you want to disable
 +this feature.
  
  =item B<TCCActivationTemp> I<Temperature>
  
 -Thermal Control Circuit Activation Temperature of the installed
 -CPU. This temperature is used when collecting the temperature of
 -cores or packages. This option should only be used if the automated
 -detection fails. Default value extracted from B<MSR_IA32_TEMPERATURE_TARGET>
 +I<Thermal Control Circuit Activation Temperature> of the installed CPU. This
 +temperature is used when collecting the temperature of cores or packages. This
 +option should only be used if the automated detection fails. Default value
 +extracted from B<MSR_IA32_TEMPERATURE_TARGET>.
  
  =item B<RunningAveragePowerLimit> I<Bitmask(Integer)>
  
 -Bitmask of the list of elements to be thermally monitored. This option
 -should only be used if the automated detection fails or if you want to
 -disable some collections. The different bits of this bitmask accepted
 -by this plugin are:
 +Bit mask of the list of elements to be thermally monitored. This option should
 +only be used if the automated detection fails or if you want to disable some
 +collections. The different bits of this bit mask accepted by this plugin are:
  
  =over 4
  
  
  =back
  
 +=item B<LogicalCoreNames> I<true>|I<false>
 +
 +Boolean enabling the use of logical core numbering for per core statistics.
 +When enabled, C<cpuE<lt>nE<gt>> is used as plugin instance, where I<n> is a
 +sequential number assigned by the kernel. Otherwise, C<coreE<lt>nE<gt>> is used
 +where I<n> is the n-th core of the socket, causing name conflicts when there is
 +more than one socket.
 +
  =back
  
  =head2 Plugin C<unixsock>
@@@ -7920,49 -7657,6 +7920,49 @@@ Example
  Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
  will be collected.
  
 +=item B<BlockDeviceFormat> B<target>|B<source>
 +
 +If I<BlockDeviceFormat> is set to B<target>, the default, then the device name
 +seen by the guest will be used for reporting metrics. 
 +This corresponds to the C<E<lt>targetE<gt>> node in the XML definition of the
 +domain.
 +
 +If I<BlockDeviceFormat> is set to B<source>, then metrics will be reported
 +using the path of the source, e.g. an image file.
 +This corresponds to the C<E<lt>sourceE<gt>> node in the XML definition of the
 +domain.
 +
 +B<Example:>
 +
 +If the domain XML have the following device defined:
 +
 +  <disk type='block' device='disk'>
 +    <driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
 +    <source dev='/var/lib/libvirt/images/image1.qcow2'/>
 +    <target dev='sda' bus='scsi'/>
 +    <boot order='2'/>
 +    <address type='drive' controller='0' bus='0' target='0' unit='0'/>
 +  </disk>
 +
 +Setting C<BlockDeviceFormat target> will cause the I<type instance> to be set
 +to C<sda>.
 +Setting C<BlockDeviceFormat source> will cause the I<type instance> to be set
 +to C<var_lib_libvirt_images_image1.qcow2>.
 +
 +=item B<BlockDeviceFormatBasename> B<false>|B<true>
 +
 +The B<BlockDeviceFormatBasename> controls whether the full path or the
 +L<basename(1)> of the source is being used as the I<type instance> when
 +B<BlockDeviceFormat> is set to B<source>. Defaults to B<false>.
 +
 +B<Example:>
 +
 +Assume the device path (source tag) is C</var/lib/libvirt/images/image1.qcow2>.
 +Setting C<BlockDeviceFormatBasename false> will cause the I<type instance> to
 +be set to C<var_lib_libvirt_images_image1.qcow2>.
 +Setting C<BlockDeviceFormatBasename true> will cause the I<type instance> to be
 +set to C<image1.qcow2>.
 +
  =item B<HostnameFormat> B<name|uuid|hostname|...>
  
  When the virt plugin logs data, it sets the hostname of the collected data
@@@ -8129,38 -7823,6 +8129,38 @@@ If set to B<true>, append the name of t
  identifier. If set to B<false> (the default), this is only done when there is
  more than one DS.
  
 +=item B<PreserveSeparator> B<false>|B<true>
 +
 +If set to B<false> (the default) the C<.> (dot) character is replaced with
 +I<EscapeCharacter>. Otherwise, if set to B<true>, the C<.> (dot) character
 +is preserved, i.e. passed through.
 +
 +=item B<DropDuplicateFields> B<false>|B<true>
 +
 +If set to B<true>, detect and remove duplicate components in Graphite metric
 +names. For example, the metric name  C<host.load.load.shortterm> will
 +be shortened to C<host.load.shortterm>.
 +
 +=back
 +
 +=head2 Plugin C<write_log>
 +
 +The C<write_log> plugin writes metrics as INFO log messages.
 +
 +This plugin supports two output formats: I<Graphite> and I<JSON>.
 +
 +Synopsis:
 +
 + <Plugin write_log>
 +   Format Graphite
 + </Plugin>
 +
 +=over 4
 +
 +=item B<Format> I<Format>
 +
 +The output format to use. Can be one of C<Graphite> or C<JSON>.
 +
  =back
  
  =head2 Plugin C<write_tsdb>
@@@ -8269,41 -7931,6 +8269,41 @@@ want to use authentication all three fi
  
  =back
  
 +=head2 Plugin C<write_prometheus>
 +
 +The I<write_prometheus plugin> implements a tiny webserver that can be scraped
 +using I<Prometheus>.
 +
 +B<Options:>
 +
 +=over 4
 +
 +=item B<Port> I<Port>
 +
 +Port the embedded webserver should listen on. Defaults to B<9103>.
 +
 +=item B<StalenessDelta> I<Seconds>
 +
 +Time in seconds after which I<Prometheus> considers a metric "stale" if it
 +hasn't seen any update for it. This value must match the setting in Prometheus.
 +It defaults to B<300> seconds (5 minutes), same as Prometheus.
 +
 +B<Background:>
 +
 +I<Prometheus> has a global setting, C<StalenessDelta>, which controls after
 +which time a metric without updates is considered "stale". This setting
 +effectively puts an upper limit on the interval in which metrics are reported.
 +
 +When the I<write_prometheus plugin> encounters a metric with an interval
 +exceeding this limit, it will inform you, the user, and provide the metric to
 +I<Prometheus> B<without> a timestamp. That causes I<Prometheus> to consider the
 +metric "fresh" each time it is scraped, with the time of the scrape being
 +considered the time of the update. The result is that there appear more
 +datapoints in I<Prometheus> than were actually created, but at least the metric
 +doesn't disappear periodically.
 +
 +=back
 +
  =head2 Plugin C<write_http>
  
  This output plugin submits values to an HTTP server using POST requests and
@@@ -8537,18 -8164,6 +8537,18 @@@ path component, for example C<host.cpu.
  default), the plugin and plugin instance (and likewise the type and type
  instance) are put into one component, for example C<host.cpu-0.cpu-idle>.
  
 +=item B<GraphiteAlwaysAppendDS> B<true>|B<false>
 +
 +If set to B<true>, append the name of the I<Data Source> (DS) to the "metric"
 +identifier. If set to B<false> (the default), this is only done when there is
 +more than one DS.
 +
 +=item B<GraphitePreserveSeparator> B<false>|B<true>
 +
 +If set to B<false> (the default) the C<.> (dot) character is replaced with
 +I<GraphiteEscapeChar>. Otherwise, if set to B<true>, the C<.> (dot) character
 +is preserved, i.e. passed through.
 +
  =item B<StoreRates> B<true>|B<false>
  
  If set to B<true> (the default), convert counter values to rates. If set to
@@@ -9483,8 -9098,6 +9483,8 @@@ Available options
  
  =item B<TypeInstance> I<Regex>
  
 +=item B<MetaData> I<String> I<Regex>
 +
  Match values where the given regular expressions match the various fields of
  the identifier of a value. If multiple regular expressions are given, B<all>
  regexen must match for a value to match.
@@@ -9773,10 -9386,6 +9773,10 @@@ Available options
  
  =item B<TypeInstance> I<Regex> I<Replacement>
  
 +=item B<MetaData> I<String> I<Regex> I<Replacement>
 +
 +=item B<DeleteMetaData> I<String> I<Regex>
 +
  Match the appropriate field with the given regular expression I<Regex>. If the
  regular expression matches, that part that matches is replaced with
  I<Replacement>. If multiple places of the input buffer match a given regular
@@@ -9815,37 -9424,9 +9815,37 @@@ Available options
  
  =item B<MetaData> I<String> I<String>
  
 -Set the appropriate field to the given string. The strings for plugin instance
 -and type instance may be empty, the strings for host and plugin may not be
 -empty. It's currently not possible to set the type of a value this way.
 +Set the appropriate field to the given string. The strings for plugin instance,
 +type instance, and meta data may be empty, the strings for host and plugin may
 +not be empty. It's currently not possible to set the type of a value this way.
 +
 +The following placeholders will be replaced by an appropriate value:
 +
 +=over 4
 +
 +=item B<%{host}>
 +
 +=item B<%{plugin}>
 +
 +=item B<%{plugin_instance}>
 +
 +=item B<%{type}>
 +
 +=item B<%{type_instance}>
 +
 +These placeholders are replaced by the identifier field of the same name.
 +
 +=item B<%{meta:>I<name>B<}>
 +
 +These placeholders are replaced by the meta data value with the given name.
 +
 +=back
 +
 +Please note that these placeholders are B<case sensitive>!
 +
 +=item B<DeleteMetaData> I<String>
 +
 +Delete the named meta data field.
  
  =back
  
diff --combined src/curl_json.c
@@@ -171,23 -171,53 +171,53 @@@ static int cj_get_type(cj_key_t *key) 
    return ds->ds[0].type;
  }
  
- static int cj_cb_map_key(void *ctx, const unsigned char *val, yajl_len_t len);
+ /* cj_load_key loads the configuration for "key" from the parent context and
+  * sets either .key or .tree in the current context. */
+ static int cj_load_key(cj_t *db, char const *key) {
+   if (db == NULL || key == NULL || db->depth <= 0)
+     return EINVAL;
  
- static void cj_cb_inc_array_index(void *ctx, _Bool update_key) {
-   cj_t *db = (cj_t *)ctx;
+   sstrncpy(db->state[db->depth].name, key, sizeof(db->state[db->depth].name));
+   c_avl_tree_t *tree = db->state[db->depth - 1].tree;
+   if (tree == NULL) {
+     return 0;
+   }
+   /* the parent has a key, so the tree pointer is invalid. */
+   if (CJ_IS_KEY(db->state[db->depth - 1].key)) {
+     return 0;
+   }
+   void *value = NULL;
+   if (c_avl_get(tree, key, (void *)&value) == 0) {
+     if (CJ_IS_KEY((cj_key_t *)value)) {
+       db->state[db->depth].key = value;
+     } else {
+       db->state[db->depth].tree = value;
+     }
+   } else if (c_avl_get(tree, CJ_ANY, (void *)&value) == 0) {
+     if (CJ_IS_KEY((cj_key_t *)value)) {
+       db->state[db->depth].key = value;
+     } else {
+       db->state[db->depth].tree = value;
+     }
+   } else {
+     db->state[db->depth].key = NULL;
+   }
+   return 0;
+ }
  
+ static void cj_advance_array(cj_t *db) {
    if (!db->state[db->depth].in_array)
      return;
  
    db->state[db->depth].index++;
  
-   if (update_key) {
-     char name[DATA_MAX_NAME_LEN];
-     ssnprintf(name, sizeof(name), "%d", db->state[db->depth].index - 1);
-     cj_cb_map_key(ctx, (unsigned char *)name, (yajl_len_t)strlen(name));
-   }
+   char name[DATA_MAX_NAME_LEN];
+   ssnprintf(name, sizeof(name), "%d", db->state[db->depth].index);
+   cj_load_key(db, name);
  }
  
  /* yajl callbacks */
  #define CJ_CB_CONTINUE 1
  
  static int cj_cb_boolean(void *ctx, int boolVal) {
-   cj_cb_inc_array_index(ctx, /* update_key = */ 0);
+   cj_advance_array(ctx);
    return (CJ_CB_CONTINUE);
  }
  
  static int cj_cb_null(void *ctx) {
-   cj_cb_inc_array_index(ctx, /* update_key = */ 0);
+   cj_advance_array(ctx);
    return (CJ_CB_CONTINUE);
  }
  
@@@ -209,40 -239,37 +239,37 @@@ static int cj_cb_number(void *ctx, cons
  
    cj_t *db = (cj_t *)ctx;
    cj_key_t *key = db->state[db->depth].key;
-   value_t vt;
-   int type;
-   int status;
  
    /* Create a null-terminated version of the string. */
    memcpy(buffer, number, number_len);
    buffer[sizeof(buffer) - 1] = 0;
  
-   if ((key == NULL) || !CJ_IS_KEY(key)) {
-     if (key != NULL &&
-         !db->state[db->depth].in_array /*can be inhomogeneous*/) {
-       NOTICE("curl_json plugin: Found \"%s\", but the configuration expects"
-              " a map.",
-              buffer);
-       return (CJ_CB_CONTINUE);
-     }
  
-     cj_cb_inc_array_index(ctx, /* update_key = */ 1);
-     key = db->state[db->depth].key;
-     if ((key == NULL) || !CJ_IS_KEY(key)) {
-       return (CJ_CB_CONTINUE);
-     }
-   } else {
-     cj_cb_inc_array_index(ctx, /* update_key = */ 1);
+   if (key == NULL) {
+     /* no config for this element. */
+     cj_advance_array(ctx);
+     return CJ_CB_CONTINUE;
+   } else if (!CJ_IS_KEY(key)) {
+     /* the config expects a map or an array. */
+     NOTICE(
+         "curl_json plugin: Found \"%s\", but the configuration expects a map.",
+         buffer);
+     cj_advance_array(ctx);
+     return CJ_CB_CONTINUE;
    }
  
-   type = cj_get_type(key);
-   status = parse_value(buffer, &vt, type);
+   int type = cj_get_type(key);
+   value_t vt;
+   int status = parse_value(buffer, &vt, type);
    if (status != 0) {
      NOTICE("curl_json plugin: Unable to parse number: \"%s\"", buffer);
+     cj_advance_array(ctx);
      return (CJ_CB_CONTINUE);
    }
  
    cj_submit(db, key, &vt);
+   cj_advance_array(ctx);
    return (CJ_CB_CONTINUE);
  } /* int cj_cb_number */
  
   * NULL. */
  static int cj_cb_map_key(void *ctx, unsigned char const *in_name,
                           yajl_len_t in_name_len) {
-   cj_t *db = (cj_t *)ctx;
-   c_avl_tree_t *tree;
+   char name[in_name_len + 1];
  
-   tree = db->state[db->depth - 1].tree;
-   if (tree != NULL) {
-     cj_key_t *value = NULL;
-     char *name;
-     size_t name_len;
-     /* Create a null-terminated version of the name. */
-     name = db->state[db->depth].name;
-     name_len =
-         COUCH_MIN((size_t)in_name_len, sizeof(db->state[db->depth].name) - 1);
-     memcpy(name, in_name, name_len);
-     name[name_len] = 0;
-     if (c_avl_get(tree, name, (void *)&value) == 0) {
-       if (CJ_IS_KEY((cj_key_t *)value)) {
-         db->state[db->depth].key = value;
-       } else {
-         db->state[db->depth].tree = (c_avl_tree_t *)value;
-       }
-     } else if (c_avl_get(tree, CJ_ANY, (void *)&value) == 0)
-       if (CJ_IS_KEY((cj_key_t *)value)) {
-         db->state[db->depth].key = value;
-       } else {
-         db->state[db->depth].tree = (c_avl_tree_t *)value;
-       }
-     else
-       db->state[db->depth].key = NULL;
-   }
+   memmove(name, in_name, in_name_len);
+   name[sizeof(name) - 1] = 0;
  
-   return (CJ_CB_CONTINUE);
+   if (cj_load_key(ctx, name) != 0)
+     return CJ_CB_ABORT;
+   return CJ_CB_CONTINUE;
  }
  
  static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
    return (cj_cb_number(ctx, (const char *)val, len));
  } /* int cj_cb_string */
  
- static int cj_cb_start(void *ctx) {
-   cj_t *db = (cj_t *)ctx;
-   if (++db->depth >= YAJL_MAX_DEPTH) {
-     ERROR("curl_json plugin: %s depth exceeds max, aborting.",
-           db->url ? db->url : db->sock);
-     return (CJ_CB_ABORT);
-   }
-   return (CJ_CB_CONTINUE);
- }
  static int cj_cb_end(void *ctx) {
    cj_t *db = (cj_t *)ctx;
    db->state[db->depth].tree = NULL;
-   --db->depth;
+   db->depth--;
+   cj_advance_array(ctx);
    return (CJ_CB_CONTINUE);
  }
  
  static int cj_cb_start_map(void *ctx) {
-   cj_cb_inc_array_index(ctx, /* update_key = */ 1);
-   return cj_cb_start(ctx);
+   cj_t *db = (cj_t *)ctx;
+   if ((db->depth + 1) >= YAJL_MAX_DEPTH) {
+     ERROR("curl_json plugin: %s depth exceeds max, aborting.",
+           db->url ? db->url : db->sock);
+     return (CJ_CB_ABORT);
+   }
+   db->depth++;
+   return (CJ_CB_CONTINUE);
  }
  
  static int cj_cb_end_map(void *ctx) { return cj_cb_end(ctx); }
  
  static int cj_cb_start_array(void *ctx) {
    cj_t *db = (cj_t *)ctx;
-   cj_cb_inc_array_index(ctx, /* update_key = */ 1);
-   if (db->depth + 1 < YAJL_MAX_DEPTH) {
-     db->state[db->depth + 1].in_array = 1;
-     db->state[db->depth + 1].index = 0;
+   if ((db->depth + 1) >= YAJL_MAX_DEPTH) {
+     ERROR("curl_json plugin: %s depth exceeds max, aborting.",
+           db->url ? db->url : db->sock);
+     return CJ_CB_ABORT;
    }
-   return cj_cb_start(ctx);
+   db->depth++;
+   db->state[db->depth].in_array = 1;
+   db->state[db->depth].index = 0;
+   cj_load_key(db, "0");
+   return CJ_CB_CONTINUE;
  }
  
  static int cj_cb_end_array(void *ctx) {
@@@ -709,11 -716,10 +716,11 @@@ static int cj_config_add_url(oconfig_it
      cb_name = ssnprintf_alloc("curl_json-%s-%s", db->instance,
                                db->url ? db->url : db->sock);
  
 -    user_data_t ud = {.data = db, .free_func = cj_free};
 -
      plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read,
 -                                 /* interval = */ db->interval, &ud);
 +                                 /* interval = */ db->interval,
 +                                 &(user_data_t){
 +                                     .data = db, .free_func = cj_free,
 +                                 });
      sfree(cb_name);
    } else {
      cj_free(db);
diff --combined src/perl.c
@@@ -22,7 -22,6 +22,7 @@@
   *
   * Authors:
   *   Sebastian Harl <sh at tokkee.org>
 + *   Pavel Rochnyak <pavel2000 ngs.ru>
   **/
  
  /*
@@@ -78,9 -77,8 +78,9 @@@
  #define PLUGIN_LOG 4
  #define PLUGIN_NOTIF 5
  #define PLUGIN_FLUSH 6
 +#define PLUGIN_FLUSH_ALL 7 /* For collectd-5.6 only */
  
 -#define PLUGIN_TYPES 7
 +#define PLUGIN_TYPES 8
  
  #define PLUGIN_CONFIG 254
  #define PLUGIN_DATASET 255
  /* this is defined in DynaLoader.a */
  void boot_DynaLoader(PerlInterpreter *, CV *);
  
 +static XS(Collectd_plugin_register_read);
 +static XS(Collectd_plugin_register_write);
 +static XS(Collectd_plugin_register_log);
 +static XS(Collectd_plugin_register_notification);
 +static XS(Collectd_plugin_register_flush);
 +static XS(Collectd_plugin_unregister_read);
 +static XS(Collectd_plugin_unregister_write);
 +static XS(Collectd_plugin_unregister_log);
 +static XS(Collectd_plugin_unregister_notification);
 +static XS(Collectd_plugin_unregister_flush);
  static XS(Collectd_plugin_register_ds);
  static XS(Collectd_plugin_unregister_ds);
  static XS(Collectd_plugin_dispatch_values);
@@@ -125,14 -113,6 +125,14 @@@ static XS(Collectd_plugin_log)
  static XS(Collectd__fc_register);
  static XS(Collectd_call_by_name);
  
 +static int perl_read(user_data_t *ud);
 +static int perl_write(const data_set_t *ds, const value_list_t *vl,
 +                      user_data_t *user_data);
 +static void perl_log(int level, const char *msg, user_data_t *user_data);
 +static int perl_notify(const notification_t *notif, user_data_t *user_data);
 +static int perl_flush(cdtime_t timeout, const char *identifier,
 +                      user_data_t *user_data);
 +
  /*
   * private data types
   */
@@@ -185,8 -165,6 +185,8 @@@ extern char **environ
   * private variables
   */
  
 +static _Bool register_legacy_flush = 1;
 +
  /* if perl_threads != NULL perl_threads->head must
   * point to the "base" thread */
  static c_ithread_list_t *perl_threads = NULL;
@@@ -203,18 -181,6 +203,18 @@@ static struct 
    char name[64];
    XS((*f));
  } api[] = {
 +    {"Collectd::plugin_register_read", Collectd_plugin_register_read},
 +    {"Collectd::plugin_register_write", Collectd_plugin_register_write},
 +    {"Collectd::plugin_register_log", Collectd_plugin_register_log},
 +    {"Collectd::plugin_register_notification",
 +     Collectd_plugin_register_notification},
 +    {"Collectd::plugin_register_flush", Collectd_plugin_register_flush},
 +    {"Collectd::plugin_unregister_read", Collectd_plugin_unregister_read},
 +    {"Collectd::plugin_unregister_write", Collectd_plugin_unregister_write},
 +    {"Collectd::plugin_unregister_log", Collectd_plugin_unregister_log},
 +    {"Collectd::plugin_unregister_notification",
 +     Collectd_plugin_unregister_notification},
 +    {"Collectd::plugin_unregister_flush", Collectd_plugin_unregister_flush},
      {"Collectd::plugin_register_data_set", Collectd_plugin_register_ds},
      {"Collectd::plugin_unregister_data_set", Collectd_plugin_unregister_ds},
      {"Collectd::plugin_dispatch_values", Collectd_plugin_dispatch_values},
@@@ -496,16 -462,15 +496,15 @@@ static int av2data_set(pTHX_ AV *array
   *   meta     => [ { name => <name>, value => <value> }, ... ]
   * }
   */
- static int av2notification_meta(pTHX_ AV *array, notification_meta_t **meta) {
-   notification_meta_t **m = meta;
+ static int av2notification_meta(pTHX_ AV *array, notification_meta_t **ret_meta) {
+   notification_meta_t *tail = NULL;
  
    int len = av_len(array);
  
    for (int i = 0; i <= len; ++i) {
      SV **tmp = av_fetch(array, i, 0);
-     HV *hash;
  
-     if (NULL == tmp)
+     if (tmp == NULL)
        return -1;
  
      if (!(SvROK(*tmp) && (SVt_PVHV == SvTYPE(SvRV(*tmp))))) {
        continue;
      }
  
-     hash = (HV *)SvRV(*tmp);
+     HV *hash = (HV *)SvRV(*tmp);
  
-     *m = smalloc(sizeof(**m));
+     notification_meta_t *m = calloc(1, sizeof(*m));
+     if (m == NULL)
+       return ENOMEM;
  
-     if (NULL == (tmp = hv_fetch(hash, "name", 4, 0))) {
+     SV **name = hv_fetch(hash, "name", strlen("name"), 0);
+     if (name == NULL) {
        log_warn("av2notification_meta: Skipping invalid "
                 "meta information.");
-       free(*m);
+       sfree(m);
        continue;
      }
-     sstrncpy((*m)->name, SvPV_nolen(*tmp), sizeof((*m)->name));
+     sstrncpy(m->name, SvPV_nolen(*name), sizeof(m->name));
  
-     if (NULL == (tmp = hv_fetch(hash, "value", 5, 0))) {
+     SV **value = hv_fetch(hash, "value", strlen("value"), 0);
+     if (value == NULL) {
        log_warn("av2notification_meta: Skipping invalid "
                 "meta information.");
-       free(*m);
+       sfree(m);
        continue;
      }
  
-     if (SvNOK(*tmp)) {
-       (*m)->nm_value.nm_double = SvNVX(*tmp);
-       (*m)->type = NM_TYPE_DOUBLE;
-     } else if (SvUOK(*tmp)) {
-       (*m)->nm_value.nm_unsigned_int = SvUVX(*tmp);
-       (*m)->type = NM_TYPE_UNSIGNED_INT;
-     } else if (SvIOK(*tmp)) {
-       (*m)->nm_value.nm_signed_int = SvIVX(*tmp);
-       (*m)->type = NM_TYPE_SIGNED_INT;
+     if (SvNOK(*value)) {
+       m->nm_value.nm_double = SvNVX(*value);
+       m->type = NM_TYPE_DOUBLE;
+     } else if (SvUOK(*value)) {
+       m->nm_value.nm_unsigned_int = SvUVX(*value);
+       m->type = NM_TYPE_UNSIGNED_INT;
+     } else if (SvIOK(*value)) {
+       m->nm_value.nm_signed_int = SvIVX(*value);
+       m->type = NM_TYPE_SIGNED_INT;
      } else {
-       (*m)->nm_value.nm_string = sstrdup(SvPV_nolen(*tmp));
-       (*m)->type = NM_TYPE_STRING;
+       m->nm_value.nm_string = sstrdup(SvPV_nolen(*value));
+       m->type = NM_TYPE_STRING;
      }
  
-     (*m)->next = NULL;
-     m = &((*m)->next);
+     m->next = NULL;
+     if (tail == NULL)
+       *ret_meta = m;
+     else
+       tail->next = m;
+     tail = m;
    }
    return 0;
  } /* static int av2notification_meta (AV *, notification_meta_t *) */
  
@@@ -999,7 -973,7 +1007,7 @@@ static int call_pv_locked(pTHX_ const c
      return 0;
    }
  
 -  ret = call_pv(sub_name, G_SCALAR);
 +  ret = call_pv(sub_name, G_SCALAR | G_EVAL);
  
    t->running = old_running;
    return ret;
  /*
   * Call all working functions of the given type.
   */
 -static int pplugin_call_all(pTHX_ int type, ...) {
 +static int pplugin_call(pTHX_ int type, ...) {
    int retvals = 0;
  
    va_list ap;
    int ret = 0;
 +  char *subname;
  
    dSP;
  
  
    PUSHMARK(SP);
  
 -  XPUSHs(sv_2mortal(newSViv((IV)type)));
 +  if (PLUGIN_READ == type) {
 +    subname = va_arg(ap, char *);
 +  } else if (PLUGIN_WRITE == type) {
 +    data_set_t *ds;
 +    value_list_t *vl;
  
 -  if (PLUGIN_WRITE == type) {
 +    AV *pds = newAV();
 +    HV *pvl = newHV();
 +
 +    subname = va_arg(ap, char *);
      /*
       * $_[0] = $plugin_type;
       *
       *   type_instance   => $type_instance
       * };
       */
 -    data_set_t *ds;
 -    value_list_t *vl;
 -
 -    AV *pds = newAV();
 -    HV *pvl = newHV();
 -
      ds = va_arg(ap, data_set_t *);
      vl = va_arg(ap, value_list_t *);
  
      XPUSHs(sv_2mortal(newRV_noinc((SV *)pds)));
      XPUSHs(sv_2mortal(newRV_noinc((SV *)pvl)));
    } else if (PLUGIN_LOG == type) {
 +    subname = va_arg(ap, char *);
      /*
       * $_[0] = $level;
       *
      XPUSHs(sv_2mortal(newSViv(va_arg(ap, int))));
      XPUSHs(sv_2mortal(newSVpv(va_arg(ap, char *), 0)));
    } else if (PLUGIN_NOTIF == type) {
 +    notification_t *n;
 +    HV *notif = newHV();
 +
 +    subname = va_arg(ap, char *);
      /*
       * $_[0] =
       * {
       *   type_instance   => $type_instance
       * };
       */
 -    notification_t *n;
 -    HV *notif = newHV();
 -
      n = va_arg(ap, notification_t *);
  
      if (-1 == notification2hv(aTHX_ n, notif)) {
      XPUSHs(sv_2mortal(newRV_noinc((SV *)notif)));
    } else if (PLUGIN_FLUSH == type) {
      cdtime_t timeout;
 +    subname = va_arg(ap, char *);
 +    /*
 +     * $_[0] = $timeout;
 +     * $_[1] = $identifier;
 +     */
 +    timeout = va_arg(ap, cdtime_t);
  
 +    XPUSHs(sv_2mortal(newSVnv(CDTIME_T_TO_DOUBLE(timeout))));
 +    XPUSHs(sv_2mortal(newSVpv(va_arg(ap, char *), 0)));
 +  } else if (PLUGIN_FLUSH_ALL == type) {
 +    cdtime_t timeout;
 +    subname = "Collectd::plugin_call_all";
      /*
       * $_[0] = $timeout;
       * $_[1] = $identifier;
       */
      timeout = va_arg(ap, cdtime_t);
  
 +    XPUSHs(sv_2mortal(newSViv((IV)PLUGIN_FLUSH)));
      XPUSHs(sv_2mortal(newSVnv(CDTIME_T_TO_DOUBLE(timeout))));
      XPUSHs(sv_2mortal(newSVpv(va_arg(ap, char *), 0)));
 +  } else if (PLUGIN_INIT == type) {
 +    subname = "Collectd::plugin_call_all";
 +    XPUSHs(sv_2mortal(newSViv((IV)type)));
 +  } else if (PLUGIN_SHUTDOWN == type) {
 +    subname = "Collectd::plugin_call_all";
 +    XPUSHs(sv_2mortal(newSViv((IV)type)));
 +  } else { /* Unknown type. Run 'plugin_call_all' and make compiler happy */
 +    subname = "Collectd::plugin_call_all";
 +    XPUSHs(sv_2mortal(newSViv((IV)type)));
    }
  
    PUTBACK;
  
 -  retvals = call_pv_locked(aTHX_ "Collectd::plugin_call_all");
 +  retvals = call_pv_locked(aTHX_ subname);
  
    SPAGAIN;
 -  if (0 < retvals) {
 +  if (SvTRUE(ERRSV)) {
 +    if (PLUGIN_LOG != type)
 +      ERROR("perl: %s error: %s", subname, SvPV_nolen(ERRSV));
 +    ret = -1;
 +  } else if (0 < retvals) {
      SV *tmp = POPs;
      if (!SvTRUE(tmp))
        ret = -1;
  
    va_end(ap);
    return ret;
 -} /* static int pplugin_call_all (int, ...) */
 +} /* static int pplugin_call (int, ...) */
  
  /*
   * collectd's Perl interpreter based thread implementation.
@@@ -1189,10 -1134,6 +1197,10 @@@ static void c_ithread_destroy(c_ithread
    assert(NULL != perl_threads);
  
    PERL_SET_CONTEXT(aTHX);
 +  /* Mark as running to avoid deadlock:
 +     c_ithread_destroy -> log_debug -> perl_log()
 +  */
 +  ithread->running = 1;
    log_debug("Shutting down Perl interpreter %p...", aTHX);
  
  #if COLLECT_DEBUG
@@@ -1406,10 -1347,7 +1414,10 @@@ static int fc_call(pTHX_ int type, int 
    }
  
    SPAGAIN;
 -  if (0 < retvals) {
 +  if (SvTRUE(ERRSV)) {
 +    ERROR("perl: Collectd::fc_call error: %s", SvPV_nolen(ERRSV));
 +    ret = -1;
 +  } else if (0 < retvals) {
      SV *tmp = POPs;
  
      /* the exec callbacks return a status, while
@@@ -1565,163 -1503,6 +1573,163 @@@ static target_proc_t ptarget = {ptarget
   * Exported Perl API.
   */
  
 +static void _plugin_register_generic_userdata(pTHX, int type,
 +                                              const char *desc) {
 +  int ret = 0;
 +  user_data_t userdata;
 +  char *pluginname;
 +
 +  dXSARGS;
 +
 +  if (2 != items) {
 +    log_err("Usage: Collectd::plugin_register_%s(pluginname, subname)", desc);
 +    XSRETURN_EMPTY;
 +  }
 +
 +  if (!SvOK(ST(0))) {
 +    log_err("Collectd::plugin_register_%s(pluginname, subname): "
 +            "Invalid pluginname",
 +            desc);
 +    XSRETURN_EMPTY;
 +  }
 +  if (!SvOK(ST(1))) {
 +    log_err("Collectd::plugin_register_%s(pluginname, subname): "
 +            "Invalid subname",
 +            desc);
 +    XSRETURN_EMPTY;
 +  }
 +
 +  /* Use pluginname as-is to allow flush a single perl plugin */
 +  pluginname = SvPV_nolen(ST(0));
 +
 +  log_debug("Collectd::plugin_register_%s: "
 +            "plugin = \"%s\", sub = \"%s\"",
 +            desc, pluginname, SvPV_nolen(ST(1)));
 +
 +  memset(&userdata, 0, sizeof(userdata));
 +  userdata.data = strdup(SvPV_nolen(ST(1)));
 +  userdata.free_func = free;
 +
 +  if (PLUGIN_READ == type) {
 +    ret = plugin_register_complex_read(
 +        "perl",                                       /* group */
 +        pluginname, perl_read, plugin_get_interval(), /* Default interval */
 +        &userdata);
 +  } else if (PLUGIN_WRITE == type) {
 +    ret = plugin_register_write(pluginname, perl_write, &userdata);
 +  } else if (PLUGIN_LOG == type) {
 +    ret = plugin_register_log(pluginname, perl_log, &userdata);
 +  } else if (PLUGIN_NOTIF == type) {
 +    ret = plugin_register_notification(pluginname, perl_notify, &userdata);
 +  } else if (PLUGIN_FLUSH == type) {
 +    if (1 == register_legacy_flush) { /* For collectd-5.7 only, #1731 */
 +      register_legacy_flush = 0;
 +      ret = plugin_register_flush("perl", perl_flush, /* user_data = */ NULL);
 +    }
 +
 +    if (0 == ret)
 +      ret = plugin_register_flush(pluginname, perl_flush, &userdata);
 +  } else {
 +    ret = -1;
 +  }
 +
 +  if (0 == ret)
 +    XSRETURN_YES;
 +  else {
 +    free(userdata.data);
 +    XSRETURN_EMPTY;
 +  }
 +} /* static void _plugin_register_generic_userdata ( ... ) */
 +
 +/*
 + * Collectd::plugin_register_TYPE (pluginname, subname).
 + *
 + * pluginname:
 + *   name of the perl plugin
 + *
 + * subname:
 + *   name of the plugin's subroutine that does the work
 + */
 +
 +static XS(Collectd_plugin_register_read) {
 +  return _plugin_register_generic_userdata(aTHX, PLUGIN_READ, "read");
 +}
 +
 +static XS(Collectd_plugin_register_write) {
 +  return _plugin_register_generic_userdata(aTHX, PLUGIN_WRITE, "write");
 +}
 +
 +static XS(Collectd_plugin_register_log) {
 +  return _plugin_register_generic_userdata(aTHX, PLUGIN_LOG, "log");
 +}
 +
 +static XS(Collectd_plugin_register_notification) {
 +  return _plugin_register_generic_userdata(aTHX, PLUGIN_NOTIF, "notification");
 +}
 +
 +static XS(Collectd_plugin_register_flush) {
 +  return _plugin_register_generic_userdata(aTHX, PLUGIN_FLUSH, "flush");
 +}
 +
 +typedef int perl_unregister_function_t(const char *name);
 +
 +static void _plugin_unregister_generic(pTHX, perl_unregister_function_t *unreg,
 +                                       const char *desc) {
 +  dXSARGS;
 +
 +  if (1 != items) {
 +    log_err("Usage: Collectd::plugin_unregister_%s(pluginname)", desc);
 +    XSRETURN_EMPTY;
 +  }
 +
 +  if (!SvOK(ST(0))) {
 +    log_err("Collectd::plugin_unregister_%s(pluginname): "
 +            "Invalid pluginname",
 +            desc);
 +    XSRETURN_EMPTY;
 +  }
 +
 +  log_debug("Collectd::plugin_unregister_%s: plugin = \"%s\"", desc,
 +            SvPV_nolen(ST(0)));
 +
 +  unreg(SvPV_nolen(ST(0)));
 +
 +  XSRETURN_EMPTY;
 +
 +  return;
 +} /* static void _plugin_unregister_generic ( ... ) */
 +
 +/*
 + * Collectd::plugin_unregister_TYPE (pluginname).
 + *
 + * TYPE:
 + *   type of callback to be unregistered: read, write, log, notification, flush
 + *
 + * pluginname:
 + *   name of the perl plugin
 + */
 +
 +static XS(Collectd_plugin_unregister_read) {
 +  return _plugin_unregister_generic(aTHX, plugin_unregister_read, "read");
 +}
 +
 +static XS(Collectd_plugin_unregister_write) {
 +  return _plugin_unregister_generic(aTHX, plugin_unregister_write, "write");
 +}
 +
 +static XS(Collectd_plugin_unregister_log) {
 +  return _plugin_unregister_generic(aTHX, plugin_unregister_log, "log");
 +}
 +
 +static XS(Collectd_plugin_unregister_notification) {
 +  return _plugin_unregister_generic(aTHX, plugin_unregister_notification,
 +                                    "notification");
 +}
 +
 +static XS(Collectd_plugin_unregister_flush) {
 +  return _plugin_unregister_generic(aTHX, plugin_unregister_flush, "flush");
 +}
 +
  /*
   * Collectd::plugin_register_data_set (type, dataset).
   *
@@@ -2097,14 -1878,14 +2105,14 @@@ static int perl_init(void) 
    assert(aTHX == perl_threads->head->interp);
    pthread_mutex_lock(&perl_threads->mutex);
  
 -  status = pplugin_call_all(aTHX_ PLUGIN_INIT);
 +  status = pplugin_call(aTHX_ PLUGIN_INIT);
  
    pthread_mutex_unlock(&perl_threads->mutex);
  
    return status;
  } /* static int perl_init (void) */
  
 -static int perl_read(void) {
 +static int perl_read(user_data_t *user_data) {
    dTHX;
  
    if (NULL == perl_threads)
  
    log_debug("perl_read: c_ithread: interp = %p (active threads: %i)", aTHX,
              perl_threads->number_of_threads);
 -  return pplugin_call_all(aTHX_ PLUGIN_READ);
 -} /* static int perl_read (void) */
 +
 +  return pplugin_call(aTHX_ PLUGIN_READ, user_data->data);
 +} /* static int perl_read (user_data_t *user_data) */
  
  static int perl_write(const data_set_t *ds, const value_list_t *vl,
 -                      user_data_t __attribute__((unused)) * user_data) {
 +                      user_data_t *user_data) {
    int status;
    dTHX;
  
  
    log_debug("perl_write: c_ithread: interp = %p (active threads: %i)", aTHX,
              perl_threads->number_of_threads);
 -  status = pplugin_call_all(aTHX_ PLUGIN_WRITE, ds, vl);
 +  status = pplugin_call(aTHX_ PLUGIN_WRITE, user_data->data, ds, vl);
  
    if (aTHX == perl_threads->head->interp)
      pthread_mutex_unlock(&perl_threads->mutex);
    return status;
  } /* static int perl_write (const data_set_t *, const value_list_t *) */
  
 -static void perl_log(int level, const char *msg,
 -                     user_data_t __attribute__((unused)) * user_data) {
 +static void perl_log(int level, const char *msg, user_data_t *user_data) {
    dTHX;
  
    if (NULL == perl_threads)
    if (aTHX == perl_threads->head->interp)
      pthread_mutex_lock(&perl_threads->mutex);
  
 -  pplugin_call_all(aTHX_ PLUGIN_LOG, level, msg);
 +  pplugin_call(aTHX_ PLUGIN_LOG, user_data->data, level, msg);
  
    if (aTHX == perl_threads->head->interp)
      pthread_mutex_unlock(&perl_threads->mutex);
    return;
  } /* static void perl_log (int, const char *) */
  
 -static int perl_notify(const notification_t *notif,
 -                       user_data_t __attribute__((unused)) * user_data) {
 +static int perl_notify(const notification_t *notif, user_data_t *user_data) {
    dTHX;
  
    if (NULL == perl_threads)
  
      aTHX = t->interp;
    }
 -  return pplugin_call_all(aTHX_ PLUGIN_NOTIF, notif);
 +  return pplugin_call(aTHX_ PLUGIN_NOTIF, user_data->data, notif);
  } /* static int perl_notify (const notification_t *) */
  
  static int perl_flush(cdtime_t timeout, const char *identifier,
 -                      user_data_t __attribute__((unused)) * user_data) {
 +                      user_data_t *user_data) {
    dTHX;
  
    if (NULL == perl_threads)
  
      aTHX = t->interp;
    }
 -  return pplugin_call_all(aTHX_ PLUGIN_FLUSH, timeout, identifier);
 +
 +  /* For collectd-5.6 only, #1731 */
 +  if (user_data == NULL || user_data->data == NULL)
 +    return pplugin_call(aTHX_ PLUGIN_FLUSH_ALL, timeout, identifier);
 +
 +  return pplugin_call(aTHX_ PLUGIN_FLUSH, user_data->data, timeout, identifier);
  } /* static int perl_flush (const int) */
  
  static int perl_shutdown(void) {
    dTHX;
  
    plugin_unregister_complex_config("perl");
 +  plugin_unregister_read_group("perl");
  
    if (NULL == perl_threads)
      return 0;
    log_debug("perl_shutdown: c_ithread: interp = %p (active threads: %i)", aTHX,
              perl_threads->number_of_threads);
  
 -  plugin_unregister_log("perl");
 -  plugin_unregister_notification("perl");
    plugin_unregister_init("perl");
 -  plugin_unregister_read("perl");
 -  plugin_unregister_write("perl");
 -  plugin_unregister_flush("perl");
 +  plugin_unregister_flush("perl"); /* For collectd-5.6 only, #1731 */
  
 -  ret = pplugin_call_all(aTHX_ PLUGIN_SHUTDOWN);
 +  ret = pplugin_call(aTHX_ PLUGIN_SHUTDOWN);
  
    pthread_mutex_lock(&perl_threads->mutex);
    t = perl_threads->tail;
@@@ -2468,7 -2248,15 +2476,7 @@@ static int init_pi(int argc, char **arg
  
    perl_run(aTHX);
  
 -  plugin_register_log("perl", perl_log, /* user_data = */ NULL);
 -  plugin_register_notification("perl", perl_notify,
 -                               /* user_data = */ NULL);
    plugin_register_init("perl", perl_init);
 -
 -  plugin_register_read("perl", perl_read);
 -
 -  plugin_register_write("perl", perl_write, /* user_data = */ NULL);
 -  plugin_register_flush("perl", perl_flush, /* user_data = */ NULL);
    plugin_register_shutdown("perl", perl_shutdown);
    return 0;
  } /* static int init_pi (const char **, const int) */
@@@ -2678,8 -2466,6 +2686,8 @@@ static int perl_config(oconfig_item_t *
        current_status = perl_config_includedir(aTHX_ c);
      else if (0 == strcasecmp(c->key, "Plugin"))
        current_status = perl_config_plugin(aTHX_ c);
 +    else if (0 == strcasecmp(c->key, "RegisterLegacyFlush"))
 +      cf_util_get_boolean(c, &register_legacy_flush);
      else {
        log_warn("Ignoring unknown config key \"%s\".", c->key);
        current_status = 0;