Update to resolve conflicts due to renamed files
authorusev6 <use_v6@aglaz.de>
Mon, 29 Apr 2019 18:03:14 +0000 (20:03 +0200)
committerusev6 <use_v6@aglaz.de>
Mon, 29 Apr 2019 18:03:14 +0000 (20:03 +0200)
Merge branch 'master' into write_graphite_reverse_host

1  2 
src/collectd.conf.in
src/collectd.conf.pod
src/utils/format_graphite/format_graphite.c
src/utils/format_graphite/format_graphite.h
src/write_graphite.c

diff --combined src/collectd.conf.in
  
  #<Plugin "intel_rdt">
  #  Cores "0-2"
+ #  Processes "sshd"
  #</Plugin>
  
  #<Plugin interface>
  #     Connection "xen:///"
  #     RefreshInterval 60
  #     Domain "name"
+ #     ReportBlockDevices true
+ #     ReportNetworkInterfaces true
  #     BlockDevice "name:device"
  #     BlockDeviceFormat target
  #     BlockDeviceFormatBasename false
  #     InterfaceDevice "name:device"
  #     IgnoreSelected false
  #     HostnameFormat name
+ #     HostnameMetadataXPath "/instance/name/text()"
+ #     HostnameMetadataNS "http://openstack.org/xmlns/libvirt/nova/1.0"
  #     InterfaceFormat name
  #     PluginInstanceFormat name
  #     Instances 1
- #     ExtraStats "cpu_util disk disk_err domain_state fs_info job_stats_background pcpu perf vcpupin"
+ #     ExtraStats "cpu_util disk disk_err domain_state fs_info job_stats_background pcpu perf vcpupin disk_physical disk_allocation disk_capacity"
  #     PersistentNotification false
  #</Plugin>
  
  #    SeparateInstances false
  #    PreserveSeparator false
  #    DropDuplicateFields false
 +#    ReverseHost false
  #  </Node>
  #</Plugin>
  
diff --combined src/collectd.conf.pod
@@@ -3498,6 -3498,7 +3498,7 @@@ B<Synopsis:
  
    <Plugin "intel_rdt">
      Cores "0-2" "3,4,6" "8-10,15"
+     Processes "sshd,qemu-system-x86" "bash"
    </Plugin>
  
  B<Options:>
@@@ -3513,11 -3514,10 +3514,10 @@@ recommended to set interval higher tha
  
  =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:
+ 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.
  
+ =item B<Processes> I<process names groups>
+ Monitoring of the events can be configured for group of processes
+ (aggregated statistics). This field defines groups of processes on which to
+ monitor supported events. The field is represented as list of strings with
+ process names group values. Each string represents a list of processes in a
+ group. Allowed format is:
+     sshd,bash,qemu
  =back
  
  B<Note:> By default global interval is used to retrieve statistics on monitored
@@@ -4444,6 -4453,12 +4453,12 @@@ For Modbus/RTU, specifies the path to t
  For Modbus/RTU, specifies the baud rate of the serial device.
  Note, connections currently support only 8/N/1.
  
+ =item B<UARTType> I<UARTType>
+ For Modbus/RTU, specifies the type of the serial device.
+ RS232, RS422 and RS485 are supported. Defaults to RS232.
+ Available only on Linux systems with libmodbus>=2.9.4.
  =item B<Interval> I<Interval>
  
  Sets the interval (in seconds) in which the values will be collected from this
@@@ -5333,8 -5348,9 +5348,9 @@@ When configuring with B<Interface> onl
  namely octets, packets, and errors. These statistics are collected by
  the C<interface> plugin, too, so using both at the same time is no benefit.
  
- When configured with B<VerboseInterface> all counters B<except> the basic ones,
- so that no data needs to be collected twice if you use the C<interface> plugin.
+ When configured with B<VerboseInterface> all counters B<except> the basic ones
+ will be collected, so that no data needs to be collected twice if you use the
+ C<interface> plugin.
  This includes dropped packets, received multicast packets, collisions and a
  whole zoo of differentiated RX and TX errors. You can try the following command
  to get an idea of what awaits you:
@@@ -6274,6 -6290,7 +6290,7 @@@ B<Synopsis:
     Address "127.0.0.1"
     Socket "/var/run/openvswitch/db.sock"
     Bridges "br0" "br_ext"
+    InterfaceStats false
   </Plugin>
  
  The plugin provides the following configuration options:
@@@ -6307,6 -6324,13 +6324,13 @@@ omitted or is empty then all OVS bridge
  
  Default: empty (monitor all bridges)
  
+ =item B<InterfaceStats> B<false>|B<true>
+ Indicates that the plugin should gather statistics for individual interfaces
+ in addition to ports.  This can be useful when monitoring an OVS setup with
+ bond ports, where you might wish to know individual statistics for the
+ interfaces included in the bonds.  Defaults to B<false>.
  =back
  
  =head2 Plugin C<pcie_errors>
@@@ -8388,26 -8412,26 +8412,26 @@@ Sets how the values are cumulated. I<Ty
  
  =item B<GaugeAverage>
  
- Calculate the average.
+ Calculate the average of all values matched during the interval.
  
  =item B<GaugeMin>
  
Use the smallest number only.
Report the smallest value matched during the interval.
  
  =item B<GaugeMax>
  
Use the greatest number only.
Report the greatest value matched during the interval.
  
  =item B<GaugeLast>
  
Use the last number found.
Report the last value matched during the interval.
  
  =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.
+ Report the last matching value. The metric is I<not> reset to C<NaN> at the end
+ of an interval. It is continuously reported until another value 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>
  
@@@ -8438,6 -8462,9 +8462,9 @@@ 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.
  
+ B<GaugeInc> is reset to I<zero> after every read, unlike other B<Gauge*>
+ metrics which are reset to C<NaN>.
  =item B<Distribution>
  
  Type to do calculations based on the distribution of values, primarily
@@@ -8511,8 -8538,12 +8538,12 @@@ The B<Gauge*> and B<Distribution> type
  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.
+ L<strtoll(3)>. B<CounterInc>, B<DeriveInc> and B<GaugeInc> do not use the
+ submatch at all and it may be omitted in this case.
+ The B<Gauge*> types, unless noted otherwise, are reset to C<NaN> after being
+ reported. In other words, B<GaugeAverage> reports the average of all values
+ matched since the last metric was reported (or C<NaN> if there was no match).
  
  =item B<Type> I<Type>
  
@@@ -9236,13 -9267,51 +9267,51 @@@ surrounded by I</.../> and collectd wa
  
  The default is to collect statistics for all domains and all their devices.
  
- Example:
+ B<Note:> B<BlockDevice> and B<InterfaceDevice> options are related to
+ corresponding B<*Format> options. Specifically, B<BlockDevice> filtering depends
+ on B<BlockDeviceFormat> setting - if user wants to filter block devices by
+ 'target' name then B<BlockDeviceFormat> option has to be set to 'target' and
+ B<BlockDevice> option must be set to a valid block device target
+ name("/:hdb/"). Mixing formats and filter values from different worlds (i.e.,
+ using 'target' name as B<BlockDevice> value with B<BlockDeviceFormat> set to
+ 'source') may lead to unexpected results (all devices filtered out or all
+ visible, depending on the value of B<IgnoreSelected> option).
+ Similarly, option B<InterfaceDevice> is related to B<InterfaceFormat> setting
+ (i.e., when user wants to use MAC address as a filter then B<InterfaceFormat>
+ has to be set to 'address' - using wrong type here may filter out all of the
+ interfaces).
+ B<Example 1:>
+ Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
+ will be collected:
  
   BlockDevice "/:hdb/"
   IgnoreSelected "true"
+  BlockDeviceFormat "target"
  
- Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
- will be collected.
+ B<Example 2:>
+ Collect metrics only for block device on 'baremetal0' domain when its
+ 'source' matches given path:
+  BlockDevice "baremetal0:/var/lib/libvirt/images/baremetal0.qcow2"
+  BlockDeviceFormat source
+ As you can see it is possible to filter devices/interfaces using
+ various formats - for block devices 'target' or 'source' name can be
+ used.  Interfaces can be filtered using 'name', 'address' or 'number'.
+ B<Example 3:>
+ Collect metrics only for domains 'baremetal0' and 'baremetal1' and
+ ignore any other domain:
+  Domain "baremetal0"
+  Domain "baremetal1"
+ It is possible to filter multiple block devices/domains/interfaces by
+ adding multiple filtering entries in separate lines.
  
  =item B<BlockDeviceFormat> B<target>|B<source>
  
@@@ -9273,6 -9342,11 +9342,11 @@@ to C<sda>
  Setting C<BlockDeviceFormat source> will cause the I<type instance> to be set
  to C<var_lib_libvirt_images_image1.qcow2>.
  
+ B<Note:> this option determines also what field will be used for
+ filtering over block devices (filter value in B<BlockDevice>
+ will be applied to target or source). More info about filtering
+ block devices can be found in the description of B<BlockDevice>.
  =item B<BlockDeviceFormatBasename> B<false>|B<true>
  
  The B<BlockDeviceFormatBasename> controls whether the full path or the
@@@ -9287,7 -9361,7 +9361,7 @@@ be set to C<var_lib_libvirt_images_imag
  Setting C<BlockDeviceFormatBasename true> will cause the I<type instance> to be
  set to C<image1.qcow2>.
  
- =item B<HostnameFormat> B<name|uuid|hostname|...>
+ =item B<HostnameFormat> B<name|uuid|hostname|metadata...>
  
  When the virt plugin logs data, it sets the hostname of the collected data
  according to this setting. The default is to use the guest name as provided by
@@@ -9300,6 -9374,9 +9374,9 @@@ B<hostname> means to use the global B<H
  useful on its own because all guests will appear to have the same name. This is
  useful in conjunction with B<PluginInstanceFormat> though.
  
+ B<metadata> means use information from guest's metadata. Use
+ B<HostnameMetadataNS> and B<HostnameMetadataXPath> to localize this information.
  You can also specify combinations of these fields. For example B<name uuid>
  means to concatenate the guest name and UUID (with a literal colon character
  between, thus I<"foo:1234-1234-1234-1234">).
@@@ -9308,7 -9385,7 +9385,7 @@@ At the moment of writing (collectd-5.5)
  characters. In case when combination of fields exceeds 62 characters,
  hostname will be truncated without a warning.
  
- =item B<InterfaceFormat> B<name>|B<address>
+ =item B<InterfaceFormat> B<name>|B<address>|B<number>
  
  When the virt plugin logs interface data, it sets the name of the collected
  data according to this setting. The default is to use the path as provided by
@@@ -9318,23 -9395,47 +9395,47 @@@ setting B<name>
  B<address> means use the interface's mac address. This is useful since the
  interface path might change between reboots of a guest or across migrations.
  
- =item B<PluginInstanceFormat> B<name|uuid|none>
+ B<number> means use the interface's number in guest.
+ B<Note:> this option determines also what field will be used for
+ filtering over interface device (filter value in B<InterfaceDevice>
+ will be applied to name, address or number).  More info about filtering
+ interfaces can be found in the description of B<InterfaceDevice>.
+ =item B<PluginInstanceFormat> B<name|uuid|metadata|none>
  
  When the virt plugin logs data, it sets the plugin_instance of the collected
  data according to this setting. The default is to not set the plugin_instance.
  
  B<name> means use the guest's name as provided by the hypervisor.
  B<uuid> means use the guest's UUID.
+ B<metadata> means use information from guest's metadata.
  
  You can also specify combinations of the B<name> and B<uuid> fields.
  For example B<name uuid> means to concatenate the guest name and UUID
  (with a literal colon character between, thus I<"foo:1234-1234-1234-1234">).
  
- =item B<Instances> B<integer>
+ =item B<HostnameMetadataNS> B<string>
  
- How many read instances you want to use for this plugin. The default is one,
- and the sensible setting is a multiple of the B<ReadThreads> value.
- If you are not sure, just use the default setting.
+ When B<metadata> is used in B<HostnameFormat> or B<PluginInstanceFormat>, this
+ selects in which metadata namespace we will pick the hostname. The default is
+ I<http://openstack.org/xmlns/libvirt/nova/1.0>.
+ =item B<HostnameMetadataXPath> B<string>
+ When B<metadata> is used in B<HostnameFormat> or B<PluginInstanceFormat>, this
+ describes where the hostname is located in the libvirt metadata. The default is
+ I</instance/name/text()>.
+ =item B<ReportBlockDevices> B<true>|B<false>
+ Enabled by default. Allows to disable stats reporting of block devices for
+ whole plugin.
+ =item B<ReportNetworkInterfaces> B<true>|B<false>
+ Enabled by default. Allows to disable stats reporting of network interfaces for
+ whole plugin.
  
  =item B<ExtraStats> B<string>
  
@@@ -9383,14 -9484,53 +9484,53 @@@ B<Note>: I<perf> metrics can't be colle
  
  =item B<vcpupin>: report pinning of domain VCPUs to host physical CPUs.
  
+ =item B<disk_physical>: report 'disk_physical' statistic for disk device.
+ B<Note>: This statistic is only reported for disk devices with 'source'
+ property available.
+ =item B<disk_allocation>: report 'disk_allocation' statistic for disk device.
+ B<Note>: This statistic is only reported for disk devices with 'source'
+ property available.
+ =item B<disk_capacity>: report 'disk_capacity' statistic for disk device.
+ B<Note>: This statistic is only reported for disk devices with 'source'
+ property available.
  =back
  
  =item B<PersistentNotification> B<true>|B<false>
  Override default configuration to only send notifications when there is a change
  in the lifecycle state of a domain. When set to true notifications will be sent
  for every read cycle. Default is false. Does not affect the stats being
  dispatched.
  
+ =item B<Instances> B<integer>
+ How many read instances you want to use for this plugin. The default is one,
+ and the sensible setting is a multiple of the B<ReadThreads> value.
+ This option is only useful when domains are specially tagged.
+ If you are not sure, just use the default setting.
+ The reader instance will only query the domains with attached matching tag.
+ Tags should have the form of 'virt-X' where X is the reader instance number,
+ starting from 0.
+ The special-purpose reader instance #0, guaranteed to be always present,
+ will query all the domains with missing or unrecognized tag, so no domain will
+ ever be left out.
+ Domain tagging is done with a custom attribute in the libvirt domain metadata
+ section. Value is selected by an XPath I</domain/metadata/ovirtmap/tag/text()>
+ expression in the I<http://ovirt.org/ovirtmap/tag/1.0> namespace.
+ (XPath and namespace values are not configurable yet).
+ Tagging could be used by management applications to evenly spread the
+ load among the reader threads, or to pin on the same threads all
+ the libvirt domains which use the same shared storage, to minimize
+ the disruption in presence of storage outages.
  =back
  
  =head2 Plugin C<vmem>
@@@ -9445,7 -9585,6 +9585,7 @@@ Synopsis
       LogSendErrors true
       Prefix "collectd"
       UseTags false
 +     ReverseHost false
     </Node>
   </Plugin>
  
@@@ -9557,30 -9696,6 +9697,30 @@@ are not used
  
  Default value: B<false>.
  
 +=item B<ReverseHost> B<false>|B<true>
 +
 +If set to B<true>, the (dot separated) parts of the B<host> field of the
 +I<value list> will be rewritten in reverse order. The rewrite happens I<before>
 +special characters are replaced with the B<EscapeCharacter>.
 +
 +This option might be convenient if the metrics are presented with Graphite in a
 +DNS like tree structure (probably without replacing dots in hostnames).
 +
 +Example:
 + Hostname "node3.cluster1.example.com"
 + LoadPlugin "cpu"
 + LoadPlugin "write_graphite"
 + <Plugin "write_graphite">
 +  <Node "graphite.example.com">
 +   EscapeCharacter "."
 +   ReverseHost true
 +  </Node>
 + </Plugin>
 +
 + result on the wire: com.example.cluster1.node3.cpu-0.cpu-idle 99.900993 1543010932
 +
 +Default value: B<false>.
 +
  =back
  
  =head2 Plugin C<write_log>
@@@ -11544,7 -11659,7 +11684,7 @@@ be an FQDN
  =head1 IGNORELISTS
  
  B<Ignorelists> are a generic framework to either ignore some metrics or report
- specific metircs only. Plugins usually provide one or more options to specify
+ specific metrics only. Plugins usually provide one or more options to specify
  the items (mounts points, devices, ...) and the boolean option
  C<IgnoreSelected>.
  
index 0000000,d0e047f..5582356
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,335 +1,381 @@@
 -  gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char, 1);
+ /**
+  * collectd - src/utils_format_graphite.c
+  * Copyright (C) 2012  Thomas Meson
+  * Copyright (C) 2012  Florian octo Forster
+  *
+  * 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
+  * Free Software Foundation; only version 2 of the License is applicable.
+  *
+  * This program is distributed in the hope that it will be useful, but
+  * WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  * You should have received a copy of the GNU General Public License along
+  * with this program; if not, write to the Free Software Foundation, Inc.,
+  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+  *
+  * Authors:
+  *   Thomas Meson <zllak at hycik.org>
+  *   Florian octo Forster <octo at collectd.org>
+  **/
+ #include "collectd.h"
+ #include "plugin.h"
+ #include "utils/common/common.h"
+ #include "utils/format_graphite/format_graphite.h"
+ #include "utils_cache.h"
+ #define GRAPHITE_FORBIDDEN " \t\"\\:!/()\n\r"
+ /* Utils functions to format data sets in graphite format.
+  * Largely taken from write_graphite.c as it remains the same formatting */
++/* helper function for reverse_hostname */
++void reverse_string(char *r_host, int len) {
++  char t;
++  for (int i = 0; i < len / 2; i++) {
++    int j = len - i - 1;
++    t = r_host[i];
++    r_host[i] = r_host[j];
++    r_host[j] = t;
++  }
++}
++
++void reverse_hostname(char *r_host, char const *orig_host) {
++  int len_host = strlen(orig_host);
++
++  /* put reversed hostname into working copy */
++  for (int i = 0; i < len_host; i++)
++    r_host[i] = orig_host[len_host - 1 - i];
++  r_host[len_host] = '\0';
++
++  /* reverse labels (except last) */
++  int p = 0;
++  for (int i = 0; i < len_host; i++)
++    if (r_host[i] == '.') {
++      reverse_string(&r_host[p], i - p);
++      p = i + 1;
++    }
++
++  /* reverse last label */
++  reverse_string(&r_host[p], len_host - p);
++}
++
+ static int gr_format_values(char *ret, size_t ret_len, int ds_num,
+                             const data_set_t *ds, const value_list_t *vl,
+                             gauge_t const *rates) {
+   size_t offset = 0;
+   int status;
+   assert(0 == strcmp(ds->type, vl->type));
+   memset(ret, 0, ret_len);
+ #define BUFFER_ADD(...)                                                        \
+   do {                                                                         \
+     status = snprintf(ret + offset, ret_len - offset, __VA_ARGS__);            \
+     if (status < 1) {                                                          \
+       return -1;                                                               \
+     } else if (((size_t)status) >= (ret_len - offset)) {                       \
+       return -1;                                                               \
+     } else                                                                     \
+       offset += ((size_t)status);                                              \
+   } while (0)
+   if (ds->ds[ds_num].type == DS_TYPE_GAUGE)
+     BUFFER_ADD(GAUGE_FORMAT, vl->values[ds_num].gauge);
+   else if (rates != NULL)
+     BUFFER_ADD("%f", rates[ds_num]);
+   else if (ds->ds[ds_num].type == DS_TYPE_COUNTER)
+     BUFFER_ADD("%" PRIu64, (uint64_t)vl->values[ds_num].counter);
+   else if (ds->ds[ds_num].type == DS_TYPE_DERIVE)
+     BUFFER_ADD("%" PRIi64, vl->values[ds_num].derive);
+   else if (ds->ds[ds_num].type == DS_TYPE_ABSOLUTE)
+     BUFFER_ADD("%" PRIu64, vl->values[ds_num].absolute);
+   else {
+     P_ERROR("gr_format_values: Unknown data source type: %i",
+             ds->ds[ds_num].type);
+     return -1;
+   }
+ #undef BUFFER_ADD
+   return 0;
+ }
+ static void gr_copy_escape_part(char *dst, const char *src, size_t dst_len,
+                                 char escape_char, bool preserve_separator) {
+   memset(dst, 0, dst_len);
+   if (src == NULL)
+     return;
+   for (size_t i = 0; i < dst_len; i++) {
+     if (src[i] == 0) {
+       dst[i] = 0;
+       break;
+     }
+     if ((!preserve_separator && (src[i] == '.')) || isspace((int)src[i]) ||
+         iscntrl((int)src[i]))
+       dst[i] = escape_char;
+     else
+       dst[i] = src[i];
+   }
+ }
+ static int gr_format_name_tagged(char *ret, int ret_len, value_list_t const *vl,
+                                  char const *ds_name, char const *prefix,
+                                  char const *postfix, char const escape_char,
+                                  unsigned int flags) {
+   char n_host[DATA_MAX_NAME_LEN];
+   char n_plugin[DATA_MAX_NAME_LEN];
+   char n_plugin_instance[DATA_MAX_NAME_LEN];
+   char n_type[DATA_MAX_NAME_LEN];
+   char n_type_instance[DATA_MAX_NAME_LEN];
+   char tmp_plugin[DATA_MAX_NAME_LEN + 8];
+   char tmp_plugin_instance[DATA_MAX_NAME_LEN + 17];
+   char tmp_type[DATA_MAX_NAME_LEN + 6];
+   char tmp_type_instance[DATA_MAX_NAME_LEN + 15];
+   char tmp_metric[3 * DATA_MAX_NAME_LEN + 2];
+   char tmp_ds_name[DATA_MAX_NAME_LEN + 9];
+   if (prefix == NULL)
+     prefix = "";
+   if (postfix == NULL)
+     postfix = "";
 -  gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char,
 -                      preserve_separator);
++  if (flags & GRAPHITE_REVERSE_HOST) {
++    int len_host = strlen(vl->host);
++    char r_host[len_host + 1];
++    reverse_hostname(r_host, vl->host);
++    gr_copy_escape_part(n_host, r_host, sizeof(n_host), escape_char, 1);
++  } else {
++    gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char, 1);
++  }
+   gr_copy_escape_part(n_plugin, vl->plugin, sizeof(n_plugin), escape_char, 1);
+   gr_copy_escape_part(n_plugin_instance, vl->plugin_instance,
+                       sizeof(n_plugin_instance), escape_char, 1);
+   gr_copy_escape_part(n_type, vl->type, sizeof(n_type), escape_char, 1);
+   gr_copy_escape_part(n_type_instance, vl->type_instance,
+                       sizeof(n_type_instance), escape_char, 1);
+   snprintf(tmp_plugin, sizeof(tmp_plugin), ";plugin=%s", n_plugin);
+   if (n_plugin_instance[0] != '\0')
+     snprintf(tmp_plugin_instance, sizeof(tmp_plugin_instance),
+              ";plugin_instance=%s", n_plugin_instance);
+   else
+     tmp_plugin_instance[0] = '\0';
+   if (!(flags & GRAPHITE_DROP_DUPE_FIELDS) || strcmp(n_plugin, n_type) != 0)
+     snprintf(tmp_type, sizeof(tmp_type), ";type=%s", n_type);
+   else
+     tmp_type[0] = '\0';
+   if (n_type_instance[0] != '\0') {
+     if (!(flags & GRAPHITE_DROP_DUPE_FIELDS) ||
+         strcmp(n_plugin_instance, n_type_instance) != 0)
+       snprintf(tmp_type_instance, sizeof(tmp_type_instance),
+                ";type_instance=%s", n_type_instance);
+     else
+       tmp_type_instance[0] = '\0';
+   } else
+     tmp_type_instance[0] = '\0';
+   /* Assert always_append_ds -> ds_name */
+   assert(!(flags & GRAPHITE_ALWAYS_APPEND_DS) || (ds_name != NULL));
+   if (ds_name != NULL) {
+     snprintf(tmp_ds_name, sizeof(tmp_ds_name), ";ds_name=%s", ds_name);
+     if ((flags & GRAPHITE_DROP_DUPE_FIELDS) && strcmp(n_plugin, n_type) == 0)
+       snprintf(tmp_metric, sizeof(tmp_metric), "%s.%s", n_plugin, ds_name);
+     else
+       snprintf(tmp_metric, sizeof(tmp_metric), "%s.%s.%s", n_plugin, n_type,
+                ds_name);
+   } else {
+     tmp_ds_name[0] = '\0';
+     if ((flags & GRAPHITE_DROP_DUPE_FIELDS) && strcmp(n_plugin, n_type) == 0)
+       snprintf(tmp_metric, sizeof(tmp_metric), "%s", n_plugin);
+     else
+       snprintf(tmp_metric, sizeof(tmp_metric), "%s.%s", n_plugin, n_type);
+   }
+   snprintf(ret, ret_len, "%s%s%s;host=%s%s%s%s%s%s", prefix, tmp_metric,
+            postfix, n_host, tmp_plugin, tmp_plugin_instance, tmp_type,
+            tmp_type_instance, tmp_ds_name);
+   return 0;
+ }
+ static int gr_format_name(char *ret, int ret_len, value_list_t const *vl,
+                           char const *ds_name, char const *prefix,
+                           char const *postfix, char const escape_char,
+                           unsigned int flags) {
+   char n_host[DATA_MAX_NAME_LEN];
+   char n_plugin[DATA_MAX_NAME_LEN];
+   char n_plugin_instance[DATA_MAX_NAME_LEN];
+   char n_type[DATA_MAX_NAME_LEN];
+   char n_type_instance[DATA_MAX_NAME_LEN];
+   char tmp_plugin[2 * DATA_MAX_NAME_LEN + 1];
+   char tmp_type[2 * DATA_MAX_NAME_LEN + 1];
+   if (prefix == NULL)
+     prefix = "";
+   if (postfix == NULL)
+     postfix = "";
+   bool preserve_separator = (flags & GRAPHITE_PRESERVE_SEPARATOR);
++  if (flags & GRAPHITE_REVERSE_HOST) {
++    int len_host = strlen(vl->host);
++    char r_host[len_host + 1];
++    reverse_hostname(r_host, vl->host);
++    gr_copy_escape_part(n_host, r_host, sizeof(n_host), escape_char,
++                        preserve_separator);
++  } else {
++    gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char,
++                        preserve_separator);
++  }
+   gr_copy_escape_part(n_plugin, vl->plugin, sizeof(n_plugin), escape_char,
+                       preserve_separator);
+   gr_copy_escape_part(n_plugin_instance, vl->plugin_instance,
+                       sizeof(n_plugin_instance), escape_char,
+                       preserve_separator);
+   gr_copy_escape_part(n_type, vl->type, sizeof(n_type), escape_char,
+                       preserve_separator);
+   gr_copy_escape_part(n_type_instance, vl->type_instance,
+                       sizeof(n_type_instance), escape_char, preserve_separator);
+   if (n_plugin_instance[0] != '\0')
+     snprintf(tmp_plugin, sizeof(tmp_plugin), "%s%c%s", n_plugin,
+              (flags & GRAPHITE_SEPARATE_INSTANCES) ? '.' : '-',
+              n_plugin_instance);
+   else
+     sstrncpy(tmp_plugin, n_plugin, sizeof(tmp_plugin));
+   if (n_type_instance[0] != '\0') {
+     if ((flags & GRAPHITE_DROP_DUPE_FIELDS) && strcmp(n_plugin, n_type) == 0)
+       sstrncpy(tmp_type, n_type_instance, sizeof(tmp_type));
+     else
+       snprintf(tmp_type, sizeof(tmp_type), "%s%c%s", n_type,
+                (flags & GRAPHITE_SEPARATE_INSTANCES) ? '.' : '-',
+                n_type_instance);
+   } else
+     sstrncpy(tmp_type, n_type, sizeof(tmp_type));
+   /* Assert always_append_ds -> ds_name */
+   assert(!(flags & GRAPHITE_ALWAYS_APPEND_DS) || (ds_name != NULL));
+   if (ds_name != NULL) {
+     if ((flags & GRAPHITE_DROP_DUPE_FIELDS) &&
+         strcmp(tmp_plugin, tmp_type) == 0)
+       snprintf(ret, ret_len, "%s%s%s.%s.%s", prefix, n_host, postfix,
+                tmp_plugin, ds_name);
+     else
+       snprintf(ret, ret_len, "%s%s%s.%s.%s.%s", prefix, n_host, postfix,
+                tmp_plugin, tmp_type, ds_name);
+   } else
+     snprintf(ret, ret_len, "%s%s%s.%s.%s", prefix, n_host, postfix, tmp_plugin,
+              tmp_type);
+   return 0;
+ }
+ static void escape_graphite_string(char *buffer, char escape_char) {
+   assert(strchr(GRAPHITE_FORBIDDEN, escape_char) == NULL);
+   for (char *head = buffer + strcspn(buffer, GRAPHITE_FORBIDDEN); *head != '\0';
+        head += strcspn(head, GRAPHITE_FORBIDDEN))
+     *head = escape_char;
+ }
+ int format_graphite(char *buffer, size_t buffer_size, data_set_t const *ds,
+                     value_list_t const *vl, char const *prefix,
+                     char const *postfix, char const escape_char,
+                     unsigned int flags) {
+   int status = 0;
+   int buffer_pos = 0;
+   gauge_t *rates = NULL;
+   if (flags & GRAPHITE_STORE_RATES) {
+     rates = uc_get_rate(ds, vl);
+     if (rates == NULL) {
+       P_ERROR("format_graphite: error with uc_get_rate");
+       return -1;
+     }
+   }
+   for (size_t i = 0; i < ds->ds_num; i++) {
+     char const *ds_name = NULL;
+     char key[10 * DATA_MAX_NAME_LEN];
+     char values[512];
+     size_t message_len;
+     char message[1024];
+     if ((flags & GRAPHITE_ALWAYS_APPEND_DS) || (ds->ds_num > 1))
+       ds_name = ds->ds[i].name;
+     /* Copy the identifier to `key' and escape it. */
+     if (flags & GRAPHITE_USE_TAGS) {
+       status = gr_format_name_tagged(key, sizeof(key), vl, ds_name, prefix,
+                                      postfix, escape_char, flags);
+       if (status != 0) {
+         P_ERROR("format_graphite: error with gr_format_name_tagged");
+         sfree(rates);
+         return status;
+       }
+     } else {
+       status = gr_format_name(key, sizeof(key), vl, ds_name, prefix, postfix,
+                               escape_char, flags);
+       if (status != 0) {
+         P_ERROR("format_graphite: error with gr_format_name");
+         sfree(rates);
+         return status;
+       }
+     }
+     escape_graphite_string(key, escape_char);
+     /* Convert the values to an ASCII representation and put that into
+      * `values'. */
+     status = gr_format_values(values, sizeof(values), i, ds, vl, rates);
+     if (status != 0) {
+       P_ERROR("format_graphite: error with gr_format_values");
+       sfree(rates);
+       return status;
+     }
+     /* Compute the graphite command */
+     message_len =
+         (size_t)snprintf(message, sizeof(message), "%s %s %u\r\n", key, values,
+                          (unsigned int)CDTIME_T_TO_TIME_T(vl->time));
+     if (message_len >= sizeof(message)) {
+       P_ERROR("format_graphite: message buffer too small: "
+               "Need %" PRIsz " bytes.",
+               message_len + 1);
+       sfree(rates);
+       return -ENOMEM;
+     }
+     /* Append it in case we got multiple data set */
+     if ((buffer_pos + message_len) >= buffer_size) {
+       P_ERROR("format_graphite: target buffer too small");
+       sfree(rates);
+       return -ENOMEM;
+     }
+     memcpy((void *)(buffer + buffer_pos), message, message_len);
+     buffer_pos += message_len;
+     buffer[buffer_pos] = '\0';
+   }
+   sfree(rates);
+   return status;
+ } /* int format_graphite */
index 0000000,60b89ae..4df7db3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,41 +1,42 @@@
+ /**
+  * collectd - src/utils_format_graphite.h
+  * Copyright (C) 2012  Thomas Meson
+  *
+  * 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
+  * Free Software Foundation; only version 2 of the License is applicable.
+  *
+  * This program is distributed in the hope that it will be useful, but
+  * WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  * You should have received a copy of the GNU General Public License along
+  * with this program; if not, write to the Free Software Foundation, Inc.,
+  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+  *
+  * Author:
+  *   Thomas Meson <zllak at hycik.org>
+  **/
+ #ifndef UTILS_FORMAT_GRAPHITE_H
+ #define UTILS_FORMAT_GRAPHITE_H 1
+ #include "collectd.h"
+ #include "plugin.h"
+ #define GRAPHITE_STORE_RATES 0x01
+ #define GRAPHITE_SEPARATE_INSTANCES 0x02
+ #define GRAPHITE_ALWAYS_APPEND_DS 0x04
+ #define GRAPHITE_DROP_DUPE_FIELDS 0x08
+ #define GRAPHITE_PRESERVE_SEPARATOR 0x10
+ #define GRAPHITE_USE_TAGS 0x20
++#define GRAPHITE_REVERSE_HOST 0x40
+ int format_graphite(char *buffer, size_t buffer_size, const data_set_t *ds,
+                     const value_list_t *vl, const char *prefix,
+                     const char *postfix, const char escape_char,
+                     unsigned int flags);
+ #endif /* UTILS_FORMAT_GRAPHITE_H */
diff --combined src/write_graphite.c
@@@ -29,7 -29,7 +29,7 @@@
   * Based on the write_http plugin.
   **/
  
 -/* write_graphite plugin configuation example
 +/* write_graphite plugin configuration example
   *
   * <Plugin write_graphite>
   *   <Carbon>
   *     LogSendErrors true
   *     Prefix "collectd"
   *     UseTags true
 + *     ReverseHost false
   *   </Carbon>
   * </Plugin>
   */
  
  #include "collectd.h"
  
- #include "common.h"
  #include "plugin.h"
+ #include "utils/common/common.h"
  
+ #include "utils/format_graphite/format_graphite.h"
  #include "utils_complain.h"
- #include "utils_format_graphite.h"
  
  #include <netdb.h>
  
@@@ -522,8 -521,6 +522,8 @@@ static int wg_config_node(oconfig_item_
        cf_util_get_flag(child, &cb->format_flags, GRAPHITE_DROP_DUPE_FIELDS);
      else if (strcasecmp("UseTags", child->key) == 0)
        cf_util_get_flag(child, &cb->format_flags, GRAPHITE_USE_TAGS);
 +    else if (strcasecmp("ReverseHost", child->key) == 0)
 +      cf_util_get_flag(child, &cb->format_flags, GRAPHITE_REVERSE_HOST);
      else if (strcasecmp("EscapeCharacter", child->key) == 0)
        config_set_char(&cb->escape_char, child);
      else {