Julien Ammous <j.ammous at gmail.com>
- Lua plugin.
+Shirly Radco <sradco at redhat.com>
+ - write_syslog plugin.
+
Kevin Bowling <kbowling at llnw.com>
- write_tsdb plugin for http://opentsdb.net/
newer libmicrohttpd. Thanks to Pavel Rochnyak. #2849
* Write Prometheus plugin: set "SO_REUSEADDRESS" on listening socket.
Thanks to Pavel Rochnyak. #2570, #2673
+ * Write Syslog plugin: The new "write_syslog" plugin writes value
+ lists as syslog messages. Thanks to Shirly Radco. #3019
2017-11-17, Version 5.8.0
* collectd: The core daemon is now completely licensed under the MIT
test_utils_time \
test_utils_vl_lookup \
test_libcollectd_network_parse \
- test_utils_config_cores
+ test_utils_config_cores \
+ test_utils_proc_pids
TESTS = $(check_PROGRAMS)
src/testing.h
test_utils_config_cores_LDADD = libplugin_mock.la
+test_utils_proc_pids_SOURCES = \
+ src/utils/proc_pids/proc_pids_test.c \
+ src/testing.h
+test_utils_proc_pids_LDADD = libplugin_mock.la
+
libavltree_la_SOURCES = \
src/utils/avltree/avltree.c \
src/utils/avltree/avltree.h
pkglib_LTLIBRARIES += intel_rdt.la
intel_rdt_la_SOURCES = \
src/intel_rdt.c \
+ src/utils/proc_pids/proc_pids.c \
+ src/utils/proc_pids/proc_pids.h \
src/utils/config_cores/config_cores.h \
src/utils/config_cores/config_cores.c
intel_rdt_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBPQOS_CPPFLAGS)
intel_rdt_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBPQOS_LDFLAGS)
intel_rdt_la_LIBADD = $(BUILD_WITH_LIBPQOS_LIBS)
+
+test_plugin_intel_rdt_SOURCES = \
+ src/intel_rdt_test.c \
+ src/utils/config_cores/config_cores.c \
+ src/utils/proc_pids/proc_pids.c \
+ src/daemon/configfile.c \
+ src/daemon/types_list.c
+test_plugin_intel_rdt_CPPFLAGS = $(AM_CPPFLAGS)
+test_plugin_intel_rdt_LDFLAGS = $(PLUGIN_LDFLAGS)
+test_plugin_intel_rdt_LDADD = liboconfig.la libplugin_mock.la
+check_PROGRAMS += test_plugin_intel_rdt
+TESTS += test_plugin_intel_rdt
endif
if BUILD_PLUGIN_INTERFACE
virt_la_LDFLAGS = $(PLUGIN_LDFLAGS)
virt_la_LIBADD = libignorelist.la $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
-test_plugin_virt_SOURCES = src/virt_test.c
+test_plugin_virt_SOURCES = src/virt_test.c src/daemon/configfile.c \
+ src/daemon/types_list.c
test_plugin_virt_CPPFLAGS = $(AM_CPPFLAGS) \
$(BUILD_WITH_LIBVIRT_CPPFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
test_plugin_virt_LDFLAGS = $(PLUGIN_LDFLAGS) \
$(BUILD_WITH_LIBVIRT_LDFLAGS) $(BUILD_WITH_LIBXML2_LDFLAGS)
-test_plugin_virt_LDADD = libplugin_mock.la \
+test_plugin_virt_LDADD = liboconfig.la libplugin_mock.la \
$(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
check_PROGRAMS += test_plugin_virt
TESTS += test_plugin_virt
$(BUILD_WITH_LIBCURL_LIBS)
endif
+if BUILD_PLUGIN_WRITE_SYSLOG
+pkglib_LTLIBRARIES += write_syslog.la
+write_syslog_la_SOURCES = src/write_syslog.c
+write_syslog_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+endif
+
if BUILD_PLUGIN_WRITE_TSDB
pkglib_LTLIBRARIES += write_tsdb.la
write_tsdb_la_SOURCES = src/write_tsdb.c
Sends data to Sensu, a stream processing and monitoring system, via the
Sensu client local TCP socket.
+ - write_syslog
+ Sends data in syslog format, using TCP, where the message
+ contains the metric in human or JSON format.
+
- write_tsdb
Sends data OpenTSDB, a scalable no master, no shared state time series
database.
# FreeBSD
if test "x$ac_system" = "xFreeBSD"; then
+ plugin_cpufreq="yes"
plugin_disk="yes"
plugin_zfs_arc="yes"
fi
AC_PLUGIN([write_riemann], [$with_libriemann_client], [Riemann output plugin])
AC_PLUGIN([write_sensu], [yes], [Sensu output plugin])
AC_PLUGIN([write_stackdriver], [$plugin_write_stackdriver], [Google Stackdriver Monitoring output plugin])
+AC_PLUGIN([write_syslog], [yes], [Syslog output plugin])
AC_PLUGIN([write_tsdb], [yes], [TSDB output plugin])
AC_PLUGIN([xencpu], [$plugin_xencpu], [Xen Host CPU usage])
AC_PLUGIN([xmms], [$with_libxmms], [XMMS statistics])
AC_MSG_RESULT([ write_riemann . . . . $enable_write_riemann])
AC_MSG_RESULT([ write_sensu . . . . . $enable_write_sensu])
AC_MSG_RESULT([ write_stackdriver . . $enable_write_stackdriver])
+AC_MSG_RESULT([ write_syslog . . . . $enable_write_syslog])
AC_MSG_RESULT([ write_tsdb . . . . . $enable_write_tsdb])
AC_MSG_RESULT([ xencpu . . . . . . . $enable_xencpu])
AC_MSG_RESULT([ xmms . . . . . . . . $enable_xmms])
%define with_write_redis 0%{!?_without_write_redis:1}
%define with_write_riemann 0%{!?_without_write_riemann:1}
%define with_write_sensu 0%{!?_without_write_sensu:1}
+%define with_write_syslog 0%{!?_without_write_syslog:1}
%define with_write_tsdb 0%{!?_without_write_tsdb:1}
%define with_xmms 0%{!?_without_xmms:0%{?_has_xmms}}
%define with_zfs_arc 0%{!?_without_zfs_arc:1}
%define _with_write_sensu --disable-write_sensu
%endif
+%if %{with_write_syslog}
+%define _with_write_syslog --enable-write_syslog
+%else
+%define _with_write_syslog --disable-write_syslog
+%endif
+
%if %{with_write_tsdb}
%define _with_write_tsdb --enable-write_tsdb
%else
%{?_with_write_redis} \
%{?_with_write_riemann} \
%{?_with_write_sensu} \
+ %{?_with_write_syslog} \
%{?_with_write_tsdb} \
%{?_with_xencpu} \
%{?_with_xmms} \
%if %{with_write_log}
%{_libdir}/%{name}/write_log.so
%endif
+%if %{with_write_syslog}
+%{_libdir}/%{name}/write_syslog.so
+%endif
%if %{with_write_sensu}
%{_libdir}/%{name}/write_sensu.so
%endif
ExecStart=/usr/sbin/collectd
EnvironmentFile=-/etc/sysconfig/collectd
EnvironmentFile=-/etc/default/collectd
-ProtectSystem=full
-ProtectHome=true
# A few plugins won't work without some privileges, which you'll have to
# specify using the CapabilityBoundingSet directive below.
per interval (see the B<Interval> configuration option of collectd). Usually
it will call B<collectd.dispatch_values> to dispatch the values to collectd
which will pass them on to all registered B<write functions>. If this function
-does not return 0 the plugin will be skipped for an increasing
-amount of time until it returns normally again.
+does not return 0, interval between its calls will grow until function returns
+0 again. See the B<MaxReadInterval> configuration option of collectd.
=item write functions
=item register_read(callback)
+Function to register read callbacks.
The callback will be called without arguments.
If this callback function does not return 0 the next call will be delayed by
an increasing interval.
-=item register_write
+=item register_write(callback)
+Function to register write callbacks.
The callback function will be called with one argument passed, which will be a
table of values.
If this callback function does not return 0 next call will be delayed by
To register those functions with collectd:
- collectd.register_read(read)
- collectd.register_write(write)
+ collectd.register_read(read) -- pass function as variable
+ collectd.register_write("write") -- pass by global-scope function name
=back
#@BUILD_PLUGIN_WRITE_RIEMANN_TRUE@LoadPlugin write_riemann
#@BUILD_PLUGIN_WRITE_SENSU_TRUE@LoadPlugin write_sensu
#@BUILD_PLUGIN_WRITE_STACKDRIVER_TRUE@LoadPlugin write_stackdriver
+#@BUILD_PLUGIN_WRITE_SYSLOG_TRUE@LoadPlugin write_syslog
#@BUILD_PLUGIN_WRITE_TSDB_TRUE@LoadPlugin write_tsdb
#@BUILD_PLUGIN_XENCPU_TRUE@LoadPlugin xencpu
#@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
#<Plugin curl>
# <Page "stock_quotes">
# URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+# AddressFamily "any"
# User "foo"
# Password "bar"
# Digest false
#<Plugin curl_json>
# <URL "http://localhost:80/test.json">
+# AddressFamily "any"
# Instance "test_http_json"
# <Key "testArray/0">
# Type "gauge"
# }
## See: http://wiki.apache.org/couchdb/Runtime_Statistics
# <URL "http://localhost:5984/_stats">
+# AddressFamily "ipv4"
# Instance "httpd"
# <Key "httpd/requests/count">
# Type "http_requests"
# </URL>
## Database status metrics:
# <URL "http://localhost:5984/_all_dbs">
+# AddressFamily "ipv6"
# Instance "dbs"
# <Key "*/doc_count">
# Type "gauge"
#<Plugin curl_xml>
# <URL "http://localhost/stats.xml">
+# AddressFamily "any"
# Host "my_host"
# #Plugin "stats"
# Instance "some_instance"
#<Plugin "intel_rdt">
# Cores "0-2"
+# Processes "sshd"
#</Plugin>
#<Plugin interface>
# SeparateInstances false
# PreserveSeparator false
# DropDuplicateFields false
+# ReverseHost false
# </Node>
#</Plugin>
# Url "https://monitoring.googleapis.com/v3"
#</Plugin>
+#<Plugin write_syslog>
+# <Node>
+# Host "localhost"
+# Port "44514"
+# Prefix "collectd"
+# MessageFormat "human"
+# HostTags ""
+# StoreRates false
+# AlwaysAppendDS false
+# </Node>
+#</Plugin>
+
#<Plugin write_tsdb>
# <Node>
# Host "localhost"
=head2 Plugin C<cpufreq>
-This plugin doesn't have any options. It reads
+This plugin is available on Linux and FreeBSD only. It doesn't have any
+options. On Linux it reads
F</sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq> (for the first CPU
installed) to get the current CPU frequency. If this file does not exist make
sure B<cpufreqd> (L<http://cpufreqd.sourceforge.net/>) or a similar tool is
installed and an "cpu governor" (that's a kernel module) is loaded.
-If the system has the I<cpufreq-stats> kernel module loaded, this plugin reports
-the rate of p-state (cpu frequency) transitions and the percentage of time spent
-in each p-state.
+On Linux, if the system has the I<cpufreq-stats> kernel module loaded, this
+plugin reports the rate of p-state (cpu frequency) transitions and the
+percentage of time spent in each p-state.
+
+On FreeBSD it does a sysctl dev.cpu.0.freq and submits this as instance 0.
+At this time FreeBSD only has one frequency setting for all cores.
+See the BUGS section in the FreeBSD man page for cpufreq(4) for more details.
+
+On FreeBSD the plugin checks the success of sysctl dev.cpu.0.freq and
+unregisters the plugin when this fails. A message will be logged to indicate
+this.
=head2 Plugin C<cpusleep>
<Page "stock_quotes">
Plugin "quotes"
URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+ AddressFamily "any"
User "foo"
Password "bar"
Digest false
URL of the web site to retrieve. Since a regular expression will be used to
extract information from this data, non-binary data is a big plus here ;)
+=item B<AddressFamily> I<Type>
+
+IP version to resolve URL to. Useful in cases when hostname in URL resolves
+to both IPv4 and IPv6 addresses, and you are interested in using one of them
+specifically.
+Use C<ipv4> to enforce IPv4, C<ipv6> to enforce IPv6, or C<any> to keep the
+default behavior of resolving addresses to all IP versions your system allows.
+If C<libcurl> is compiled without IPv6 support, using C<ipv6> will result in
+a warning and fallback to C<any>.
+If C<Type> cannot be parsed, a warning will be printed and the whole B<Page>
+block will be ignored.
+
=item B<User> I<Name>
Username to use if authorization is required to read the page.
<Plugin curl_json>
<URL "http://localhost:5984/_stats">
+ AddressFamily "any"
Instance "httpd"
<Key "httpd/requests/count">
Type "http_requests"
=over 4
+=item B<AddressFamily> I<Type>
+
+IP version to resolve URL to. Useful in cases when hostname in URL resolves
+to both IPv4 and IPv6 addresses, and you are interested in using one of them
+specifically.
+Use C<ipv4> to enforce IPv4, C<ipv6> to enforce IPv6, or C<any> to keep the
+default behavior of resolving addresses to all IP versions your system allows.
+If C<libcurl> is compiled without IPv6 support, using C<ipv6> will result in
+a warning and fallback to C<any>.
+If C<Type> cannot be parsed, a warning will be printed and the whole B<URL>
+block will be ignored.
+
=item B<Host> I<Name>
Use I<Name> as the host name when submitting values. Defaults to the global
<Plugin "curl_xml">
<URL "http://localhost/stats.xml">
+ AddressFamily "any"
Host "my_host"
#Plugin "curl_xml"
Instance "some_instance"
=over 4
+=item B<AddressFamily> I<Type>
+
+IP version to resolve URL to. Useful in cases when hostname in URL resolves
+to both IPv4 and IPv6 addresses, and you are interested in using one of them
+specifically.
+Use C<ipv4> to enforce IPv4, C<ipv6> to enforce IPv6, or C<any> to keep the
+default behavior of resolving addresses to all IP versions your system allows.
+If C<libcurl> is compiled without IPv6 support, using C<ipv6> will result in
+a warning and fallback to C<any>.
+If C<Type> cannot be parsed, a warning will be printed and the whole B<URL>
+block will be ignored.
+
=item B<Host> I<Name>
Use I<Name> as the host name when submitting values. Defaults to the global
<Plugin "intel_rdt">
Cores "0-2" "3,4,6" "8-10,15"
+ Processes "sshd,qemu-system-x86" "bash"
</Plugin>
B<Options:>
=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
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
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>
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
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
=item B<disk_err>: report disk errors if any occured. Requires libvirt API version
I<0.9.10> or later.
-=item B<domain_state>: report domain state and reason in human-readable format as
-a notification. If libvirt API version I<0.9.2> or later is available, domain
-reason will be included in notification.
+=item B<domain_state>: report domain state and reason as 'domain_state' metric.
=item B<fs_info>: report file system information as a notification. Requires
libvirt API version I<1.2.11> or later. Can be collected only if I<Guest Agent>
LogSendErrors true
Prefix "collectd"
UseTags false
+ ReverseHost false
</Node>
</Plugin>
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>
=back
+=head2 Plugin C<write_syslog>
+
+The C<write_syslog> plugin writes data in I<syslog> format log messages.
+It implements the basic syslog protocol, RFC 5424, extends it with
+content-based filtering, rich filtering capabilities,
+flexible configuration options and adds features such as using TCP for transport.
+The plugin can connect to a I<Syslog> daemon, like syslog-ng and rsyslog, that will
+ingest metrics, transform and ship them to the specified output.
+The plugin uses I<TCP> over the "line based" protocol with a default port 44514.
+The data will be sent in blocks of at most 1428 bytes to minimize the number of
+network packets.
+
+Synopsis:
+
+ <Plugin write_syslog>
+ ResolveInterval 60
+ ResolveJitter 60
+ <Node "example">
+ Host "syslog-1.my.domain"
+ Port "44514"
+ Prefix "collectd"
+ MessageFormat "human"
+ HostTags ""
+ </Node>
+ </Plugin>
+
+The configuration consists of one or more E<lt>B<Node>E<nbsp>I<Name>E<gt>
+blocks and global directives.
+
+Global directives are:
+
+=over 4
+
+=item B<ResolveInterval> I<seconds>
+
+=item B<ResolveJitter> I<seconds>
+
+When I<collectd> connects to a syslog node, it will request the hostname from
+DNS. This can become a problem if the syslog node is unavailable or badly
+configured because collectd will request DNS in order to reconnect for every
+metric, which can flood your DNS. So you can cache the last value for
+I<ResolveInterval> seconds.
+Defaults to the I<Interval> of the I<write_syslog plugin>, e.g. 10E<nbsp>seconds.
+
+You can also define a jitter, a random interval to wait in addition to
+I<ResolveInterval>. This prevents all your collectd servers to resolve the
+hostname at the same time when the connection fails.
+Defaults to the I<Interval> of the I<write_syslog plugin>, e.g. 10E<nbsp>seconds.
+
+B<Note:> If the DNS resolution has already been successful when the socket
+closes, the plugin will try to reconnect immediately with the cached
+information. DNS is queried only when the socket is closed for a longer than
+I<ResolveInterval> + I<ResolveJitter> seconds.
+
+=back
+
+Inside the B<Node> blocks, the following options are recognized:
+
+=over 4
+
+=item B<Host> I<Address>
+
+Hostname or address to connect to. Defaults to C<localhost>.
+
+=item B<Port> I<Service>
+
+Service name or port number to connect to. Defaults to C<44514>.
+
+
+=item B<HostTags> I<String>
+
+When set, I<HostTags> is added to the end of the metric.
+It is intended to be used for adding additional metadata to tag the metric with.
+Dots and whitespace are I<not> escaped in this string.
+
+Examples:
+
+When MessageFormat is set to "human".
+
+ ["prefix1" "example1"="example1_v"]["prefix2" "example2"="example2_v"]"
+
+When MessageFormat is set to "JSON", text should be in JSON format.
+Escaping the quotation marks is required.
+
+ HostTags "\"prefix1\": {\"example1\":\"example1_v\",\"example2\":\"example2_v\"}"
+
+=item B<MessageFormat> I<String>
+
+I<MessageFormat> selects the format in which messages are sent to the
+syslog deamon, human or JSON. Defaults to human.
+
+Syslog message format:
+
+<priority>VERSION ISOTIMESTAMP HOSTNAME APPLICATION PID MESSAGEID STRUCTURED-DATA MSG
+
+The difference between the message formats are in the STRUCTURED-DATA and MSG parts.
+
+Human format:
+
+ <166>1 ISOTIMESTAMP HOSTNAME collectd PID MESSAGEID
+ ["collectd" "value": "v1" "plugin"="plugin_v" "plugin_instance"="plugin_instance_v"
+ "type_instance"="type_instance_v" "type"="type_v" "ds_name"="ds_name_v" "interval"="interval_v" ]
+ "host_tag_example"="host_tag_example_v" plugin_v.type_v.ds_name_v="v1"
+
+JSON format:
+
+ <166>1 ISOTIMESTAMP HOSTNAME collectd PID MESSAGEID STRUCTURED-DATA
+ {
+ "collectd": {
+ "time": time_as_epoch, "interval": interval_v, "plugin": "plugin_v",
+ "plugin_instance": "plugin_instance_v", "type":"type_v",
+ "type_instance": "type_instance_v", "plugin_v": {"type_v": v1}
+ } , "host":"host_v", "host_tag_example": "host_tag_example_v"
+ }
+
+=item B<StoreRates> B<false>|B<true>
+
+If set to B<true>, convert counter values to rates. If set to B<false>
+(the default) counter values are stored as is, as an increasing
+integer number.
+
+=item B<AlwaysAppendDS> B<false>|B<true>
+
+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<Prefix> I<String>
+
+When set, I<Prefix> is added to all metrics names as a prefix. It is intended in
+case you want to be able to define the source of the specific metric. Dots and
+whitespace are I<not> escaped in this string.
+
+=back
+
=head2 Plugin C<xencpu>
This plugin collects metrics of hardware CPU load for machine running Xen
#include "plugin.h"
#include "utils/common/common.h"
+#if KERNEL_FREEBSD
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+
+#if KERNEL_LINUX
#define MAX_AVAIL_FREQS 20
static int num_cpu;
}
return;
}
+#endif /* KERNEL_LINUX */
static int cpufreq_init(void) {
+#if KERNEL_LINUX
char filename[PATH_MAX];
num_cpu = 0;
if (num_cpu == 0)
plugin_unregister_read("cpufreq");
+#elif KERNEL_FREEBSD
+ char mib[] = "dev.cpu.0.freq";
+ int cpufreq;
+ size_t cf_len = sizeof(cpufreq);
+
+ if (sysctlbyname(mib, &cpufreq, &cf_len, NULL, 0) != 0) {
+ WARNING("cpufreq plugin: sysctl \"%s\" failed.", mib);
+ plugin_unregister_read("cpufreq");
+ }
+#endif
return 0;
} /* int cpufreq_init */
plugin_dispatch_values(&vl);
}
+#if KERNEL_LINUX
static void cpufreq_read_stats(int cpu) {
char filename[PATH_MAX];
/* Read total transitions for cpu frequency */
}
fclose(fh);
}
+#endif /* KERNEL_LINUX */
static int cpufreq_read(void) {
+#if KERNEL_LINUX
for (int cpu = 0; cpu < num_cpu; cpu++) {
char filename[PATH_MAX];
/* Read cpu frequency */
if (report_p_stats)
cpufreq_read_stats(cpu);
}
+#elif KERNEL_FREEBSD
+ /* FreeBSD currently only has 1 freq setting. See BUGS in cpufreq(4) */
+ char mib[] = "dev.cpu.0.freq";
+ int cpufreq;
+ size_t cf_len = sizeof(cpufreq);
+
+ if (sysctlbyname(mib, &cpufreq, &cf_len, NULL, 0) != 0) {
+ WARNING("cpufreq plugin: sysctl \"%s\" failed.", mib);
+ return 0;
+ }
+
+ value_t v;
+ /* convert Mhz to Hz */
+ v.gauge = cpufreq * 1000000.0;
+
+ cpufreq_submit(0, "cpufreq", NULL, &v);
+#endif
return 0;
} /* int cpufreq_read */
char *instance;
char *url;
+ int address_family;
char *user;
char *pass;
char *credentials;
curl_easy_setopt(wp->curl, CURLOPT_ERRORBUFFER, wp->curl_errbuf);
curl_easy_setopt(wp->curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(wp->curl, CURLOPT_MAXREDIRS, 50L);
+ curl_easy_setopt(wp->curl, CURLOPT_IPRESOLVE, wp->address_family);
if (wp->user != NULL) {
#ifdef HAVE_CURLOPT_USERNAME
}
page->plugin_name = NULL;
page->url = NULL;
+ page->address_family = CURL_IPRESOLVE_WHATEVER;
page->user = NULL;
page->pass = NULL;
page->digest = false;
status = cf_util_get_string(child, &page->plugin_name);
else if (strcasecmp("URL", child->key) == 0)
status = cf_util_get_string(child, &page->url);
- else if (strcasecmp("User", child->key) == 0)
+ else if (strcasecmp("AddressFamily", child->key) == 0) {
+ char *af = NULL;
+ status = cf_util_get_string(child, &af);
+ if (status != 0 || af == NULL) {
+ WARNING("curl plugin: Cannot parse value of `%s' "
+ "for instance `%s'.",
+ child->key, page->instance);
+ } else if (strcasecmp("any", af) == 0) {
+ page->address_family = CURL_IPRESOLVE_WHATEVER;
+ } else if (strcasecmp("ipv4", af) == 0) {
+ page->address_family = CURL_IPRESOLVE_V4;
+ } else if (strcasecmp("ipv6", af) == 0) {
+ /* If curl supports ipv6, use it. If not, log a warning and
+ * fall back to default - don't set status to non-zero.
+ */
+ curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
+ if (curl_info->features & CURL_VERSION_IPV6)
+ page->address_family = CURL_IPRESOLVE_V6;
+ else
+ WARNING("curl plugin: IPv6 not supported by this libCURL. "
+ "Using fallback `any'.");
+ } else {
+ WARNING("curl plugin: Unsupported value of `%s' "
+ "for instance `%s'.",
+ child->key, page->instance);
+ status = -1;
+ }
+ } else if (strcasecmp("User", child->key) == 0)
status = cf_util_get_string(child, &page->user);
else if (strcasecmp("Password", child->key) == 0)
status = cf_util_get_string(child, &page->pass);
char *sock;
char *url;
+ int address_family;
char *user;
char *pass;
char *credentials;
curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
+ curl_easy_setopt(db->curl, CURLOPT_IPRESOLVE, db->address_family);
if (db->user != NULL) {
#ifdef HAVE_CURLOPT_USERNAME
}
db->timeout = -1;
+ db->address_family = CURL_IPRESOLVE_WHATEVER;
if (strcasecmp("URL", ci->key) == 0)
status = cf_util_get_string(ci, &db->url);
db->stats = curl_stats_from_config(child);
if (db->stats == NULL)
status = -1;
+ } else if (db->url && strcasecmp("AddressFamily", child->key) == 0) {
+ char *af = NULL;
+ status = cf_util_get_string(child, &af);
+ if (status != 0 || af == NULL) {
+ WARNING("curl_json plugin: Cannot parse value of `%s' for URL `%s'.",
+ child->key, db->url);
+ } else if (strcasecmp("any", af) == 0) {
+ db->address_family = CURL_IPRESOLVE_WHATEVER;
+ } else if (strcasecmp("ipv4", af) == 0) {
+ db->address_family = CURL_IPRESOLVE_V4;
+ } else if (strcasecmp("ipv6", af) == 0) {
+ /* If curl supports ipv6, use it. If not, log a warning and
+ * fall back to default - don't set status to non-zero.
+ */
+ curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
+ if (curl_info->features & CURL_VERSION_IPV6)
+ db->address_family = CURL_IPRESOLVE_V6;
+ else
+ WARNING("curl_json plugin: IPv6 not supported by this libCURL. "
+ "Using fallback `any'.");
+ } else {
+ WARNING("curl_json plugin: Unsupported value of `%s' for URL `%s'.",
+ child->key, db->url);
+ status = -1;
+ }
} else {
WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
status = -1;
char *host;
char *url;
+ int address_family;
char *user;
char *pass;
char *credentials;
curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
+ curl_easy_setopt(db->curl, CURLOPT_IPRESOLVE, db->address_family);
if (db->user != NULL) {
#ifdef HAVE_CURLOPT_USERNAME
}
db->timeout = -1;
+ db->address_family = CURL_IPRESOLVE_WHATEVER;
int status = cf_util_get_string(ci, &db->url);
if (status != 0) {
db->stats = curl_stats_from_config(child);
if (db->stats == NULL)
status = -1;
+ } else if (strcasecmp("AddressFamily", child->key) == 0) {
+ char *af = NULL;
+ status = cf_util_get_string(child, &af);
+ if (status != 0 || af == NULL) {
+ WARNING("curl_xml plugin: Cannot parse value of `%s' for URL `%s'.",
+ child->key, db->url);
+ } else if (strcasecmp("any", af) == 0) {
+ db->address_family = CURL_IPRESOLVE_WHATEVER;
+ } else if (strcasecmp("ipv4", af) == 0) {
+ db->address_family = CURL_IPRESOLVE_V4;
+ } else if (strcasecmp("ipv6", af) == 0) {
+ /* If curl supports ipv6, use it. If not, log a warning and
+ * fall back to default - don't set status to non-zero.
+ */
+ curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
+ if (curl_info->features & CURL_VERSION_IPV6)
+ db->address_family = CURL_IPRESOLVE_V6;
+ else
+ WARNING("curl_xml plugin: IPv6 not supported by this libCURL. "
+ "Using fallback `any'.");
+ } else {
+ WARNING("curl_xml plugin: Unsupported value of `%s' for URL `%s'.",
+ child->key, db->url);
+ status = -1;
+ }
} else {
WARNING("curl_xml plugin: Option `%s' not allowed here.", child->key);
status = -1;
/**
* collectd - src/intel_rdt.c
*
- * Copyright(c) 2016-2018 Intel Corporation. All rights reserved.
+ * Copyright(c) 2016-2019 Intel Corporation. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* Authors:
* Serhiy Pshyk <serhiyx.pshyk@intel.com>
+ * Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ * Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ * Michał Aleksiński <michalx.aleksinski@intel.com>
**/
#include "collectd.h"
#include "utils/common/common.h"
#include "utils/config_cores/config_cores.h"
-
+#include "utils/proc_pids/proc_pids.h"
#include <pqos.h>
#define RDT_PLUGIN "intel_rdt"
+/* libpqos v2.0 or newer is required for process monitoring*/
+#undef LIBPQOS2
+#if defined(PQOS_VERSION) && PQOS_VERSION >= 20000
+#define LIBPQOS2
+#endif
+
+#define RDT_PLUGIN "intel_rdt"
+
#define RDT_MAX_SOCKETS 8
#define RDT_MAX_SOCKET_CORES 64
#define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
+#ifdef LIBPQOS2
+/*
+ * Process name inside comm file is limited to 16 chars.
+ * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
+ */
+#define RDT_MAX_NAMES_GROUPS 64
+#define RDT_PROC_PATH "/proc"
+#endif /* LIBPQOS2 */
+
typedef enum {
UNKNOWN = 0,
CONFIGURATION_ERROR,
} rdt_config_status;
+#ifdef LIBPQOS2
+struct rdt_name_group_s {
+ char *desc;
+ size_t num_names;
+ char **names;
+ proc_pids_t **proc_pids;
+ size_t monitored_pids_count;
+ enum pqos_mon_event events;
+};
+typedef struct rdt_name_group_s rdt_name_group_t;
+#endif /* LIBPQOS2 */
+
struct rdt_ctx_s {
core_groups_list_t cores;
enum pqos_mon_event events[RDT_MAX_CORES];
- struct pqos_mon_data *pgroups[RDT_MAX_CORES];
- size_t num_groups;
+ struct pqos_mon_data *pcgroups[RDT_MAX_CORES];
+#ifdef LIBPQOS2
+ rdt_name_group_t ngroups[RDT_MAX_NAMES_GROUPS];
+ struct pqos_mon_data *pngroups[RDT_MAX_NAMES_GROUPS];
+ size_t num_ngroups;
+ proc_pids_t **proc_pids;
+ size_t num_proc_pids;
+#endif /* LIBPQOS2 */
const struct pqos_cpuinfo *pqos_cpu;
const struct pqos_cap *pqos_cap;
const struct pqos_capability *cap_mon;
static rdt_config_status g_state = UNKNOWN;
+static int g_interface = -1;
+
+static void rdt_submit_derive(const char *cgroup, const char *type,
+ const char *type_instance, derive_t value) {
+ value_list_t vl = VALUE_LIST_INIT;
+
+ vl.values = &(value_t){.derive = value};
+ vl.values_len = 1;
+
+ sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+ snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+ sstrncpy(vl.type, type, sizeof(vl.type));
+ if (type_instance)
+ sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+ plugin_dispatch_values(&vl);
+}
+
+static void rdt_submit_gauge(const char *cgroup, const char *type,
+ const char *type_instance, gauge_t value) {
+ value_list_t vl = VALUE_LIST_INIT;
+
+ vl.values = &(value_t){.gauge = value};
+ vl.values_len = 1;
+
+ sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+ snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+ sstrncpy(vl.type, type, sizeof(vl.type));
+ if (type_instance)
+ sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+ plugin_dispatch_values(&vl);
+}
+
#if COLLECT_DEBUG
static void rdt_dump_cgroups(void) {
char cores[RDT_MAX_CORES * 4];
return;
DEBUG(RDT_PLUGIN ": Core Groups Dump");
- DEBUG(RDT_PLUGIN ": groups count: %" PRIsz, g_rdt->num_groups);
+ DEBUG(RDT_PLUGIN ": groups count: %" PRIsz, g_rdt->cores.num_cgroups);
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
core_group_t *cgroup = g_rdt->cores.cgroups + i;
memset(cores, 0, sizeof(cores));
return;
}
+#ifdef LIBPQOS2
+static void rdt_dump_ngroups(void) {
+
+ char names[DATA_MAX_NAME_LEN];
+
+ if (g_rdt == NULL)
+ return;
+
+ DEBUG(RDT_PLUGIN ": Process Names Groups Dump");
+ DEBUG(RDT_PLUGIN ": groups count: %" PRIsz, g_rdt->num_ngroups);
+
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+ memset(names, 0, sizeof(names));
+ for (size_t j = 0; j < g_rdt->ngroups[i].num_names; j++)
+ snprintf(names + strlen(names), sizeof(names) - strlen(names) - 1, " %s",
+ g_rdt->ngroups[i].names[j]);
+
+ DEBUG(RDT_PLUGIN ": group[%d]:", (int)i);
+ DEBUG(RDT_PLUGIN ": description: %s", g_rdt->ngroups[i].desc);
+ DEBUG(RDT_PLUGIN ": process names:%s", names);
+ DEBUG(RDT_PLUGIN ": events: 0x%X", g_rdt->ngroups[i].events);
+ }
+
+ return;
+}
+#endif /* LIBPQOS2 */
+
static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
static inline double bytes_to_mb(const double bytes) {
return bytes / (1024.0 * 1024.0);
}
-static void rdt_dump_data(void) {
+static void rdt_dump_cores_data(void) {
+/*
+ * CORE - monitored group of cores
+ * RMID - Resource Monitoring ID associated with the monitored group
+ * This is not available for monitoring with resource control
+ * LLC - last level cache occupancy
+ * MBL - local memory bandwidth
+ * MBR - remote memory bandwidth
+ */
+#ifdef LIBPQOS2
+ if (g_interface == PQOS_INTER_OS_RESCTRL_MON) {
+ DEBUG(RDT_PLUGIN ": CORE LLC[KB] MBL[MB] MBR[MB]");
+ } else {
+ DEBUG(RDT_PLUGIN ": CORE RMID LLC[KB] MBL[MB] MBR[MB]");
+ }
+#else
+ DEBUG(RDT_PLUGIN ": CORE RMID LLC[KB] MBL[MB] MBR[MB]");
+#endif /* LIBPQOS2 */
+
+ for (int i = 0; i < g_rdt->cores.num_cgroups; i++) {
+ const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
+
+ double llc = bytes_to_kb(pv->llc);
+ double mbr = bytes_to_mb(pv->mbm_remote_delta);
+ double mbl = bytes_to_mb(pv->mbm_local_delta);
+#ifdef LIBPQOS2
+ if (g_interface == PQOS_INTER_OS_RESCTRL_MON) {
+ DEBUG(RDT_PLUGIN ": [%s] %10.1f %10.1f %10.1f",
+ g_rdt->cores.cgroups[i].desc, llc, mbl, mbr);
+ } else {
+ DEBUG(RDT_PLUGIN ": [%s] %8u %10.1f %10.1f %10.1f",
+ g_rdt->cores.cgroups[i].desc, g_rdt->pcgroups[i]->poll_ctx[0].rmid,
+ llc, mbl, mbr);
+ }
+#else
+ DEBUG(RDT_PLUGIN ": [%s] %8u %10.1f %10.1f %10.1f",
+ g_rdt->cores.cgroups[i].desc, g_rdt->pcgroups[i]->poll_ctx[0].rmid,
+ llc, mbl, mbr);
+#endif /* LIBPQOS2 */
+ }
+}
+
+#ifdef LIBPQOS2
+static void rdt_dump_pids_data(void) {
/*
- * CORE - monitored group of cores
- * RMID - Resource Monitoring ID associated with the monitored group
+ * NAME - monitored group of processes
+ * PIDs - list of PID numbers in the NAME group
* LLC - last level cache occupancy
* MBL - local memory bandwidth
* MBR - remote memory bandwidth
*/
- DEBUG(" CORE RMID LLC[KB] MBL[MB] MBR[MB]");
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
- const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+
+ DEBUG(RDT_PLUGIN ": NAME PIDs");
+ char pids[DATA_MAX_NAME_LEN];
+ for (size_t i = 0; i < g_rdt->num_ngroups; ++i) {
+ memset(pids, 0, sizeof(pids));
+ for (size_t j = 0; j < g_rdt->ngroups[i].num_names; ++j) {
+ pids_list_t *list = g_rdt->ngroups[i].proc_pids[j]->curr;
+ for (size_t k = 0; k < list->size; k++)
+ snprintf(pids + strlen(pids), sizeof(pids) - strlen(pids) - 1, " %u",
+ list->pids[k]);
+ }
+ DEBUG(RDT_PLUGIN ": [%s] %s", g_rdt->ngroups[i].desc, pids);
+ }
+
+ DEBUG(RDT_PLUGIN ": NAME LLC[KB] MBL[MB] MBR[MB]");
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+
+ const struct pqos_event_values *pv = &g_rdt->pngroups[i]->values;
double llc = bytes_to_kb(pv->llc);
double mbr = bytes_to_mb(pv->mbm_remote_delta);
double mbl = bytes_to_mb(pv->mbm_local_delta);
- DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cores.cgroups[i].desc,
- g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
+ DEBUG(RDT_PLUGIN ": [%s] %10.1f %10.1f %10.1f", g_rdt->ngroups[i].desc,
+ llc, mbl, mbr);
+ }
+}
+#endif /* LIBPQOS2 */
+#endif /* COLLECT_DEBUG */
+
+#ifdef LIBPQOS2
+static int isdupstr(const char *names[], const size_t size, const char *name) {
+ for (size_t i = 0; i < size; i++)
+ if (strncmp(names[i], name, (size_t)MAX_PROC_NAME_LEN) == 0)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * NAME
+ * strlisttoarray
+ *
+ * DESCRIPTION
+ * Converts string representing list of strings into array of strings.
+ * Allowed format is:
+ * name,name1,name2,name3
+ *
+ * PARAMETERS
+ * `str_list' String representing list of strings.
+ * `names' Array to put extracted strings into.
+ * `names_num' Variable to put number of extracted strings.
+ *
+ * RETURN VALUE
+ * Number of elements placed into names.
+ */
+static int strlisttoarray(char *str_list, char ***names, size_t *names_num) {
+ char *saveptr = NULL;
+
+ if (str_list == NULL || names == NULL)
+ return -EINVAL;
+
+ if (strstr(str_list, ",,")) {
+ /* strtok ignores empty words between separators.
+ * This condition handles that by rejecting strings
+ * with consecutive seprators */
+ ERROR(RDT_PLUGIN ": Empty process name");
+ return -EINVAL;
+ }
+
+ for (;;) {
+ char *token = strtok_r(str_list, ",", &saveptr);
+ if (token == NULL)
+ break;
+
+ str_list = NULL;
+
+ while (isspace(*token))
+ token++;
+
+ if (*token == '\0')
+ continue;
+
+ if ((isdupstr((const char **)*names, *names_num, token))) {
+ ERROR(RDT_PLUGIN ": Duplicated process name \'%s\' in group \'%s\'",
+ token, str_list);
+ return -EINVAL;
+ } else {
+ if (0 != strarray_add(names, names_num, token)) {
+ ERROR(RDT_PLUGIN ": Error allocating process name string");
+ return -ENOMEM;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * NAME
+ * ngroup_cmp
+ *
+ * DESCRIPTION
+ * Function to compare names in two name groups.
+ *
+ * PARAMETERS
+ * `ng_a' Pointer to name group a.
+ * `ng_b' Pointer to name group b.
+ *
+ * RETURN VALUE
+ * 1 if both groups contain the same names
+ * 0 if none of their names match
+ * -1 if some but not all names match
+ */
+static int ngroup_cmp(const rdt_name_group_t *ng_a,
+ const rdt_name_group_t *ng_b) {
+ unsigned found = 0;
+
+ assert(ng_a != NULL);
+ assert(ng_b != NULL);
+
+ const size_t sz_a = (unsigned)ng_a->num_names;
+ const size_t sz_b = (unsigned)ng_b->num_names;
+ const char **tab_a = (const char **)ng_a->names;
+ const char **tab_b = (const char **)ng_b->names;
+
+ for (size_t i = 0; i < sz_a; i++) {
+ for (size_t j = 0; j < sz_b; j++)
+ if (strncmp(tab_a[i], tab_b[j], (size_t)MAX_PROC_NAME_LEN) == 0)
+ found++;
+ }
+ /* if no names are the same */
+ if (!found)
+ return 0;
+ /* if group contains same names */
+ if (sz_a == sz_b && sz_b == (size_t)found)
+ return 1;
+ /* if not all names are the same */
+ return -1;
+}
+
+/*
+ * NAME
+ * oconfig_to_ngroups
+ *
+ * DESCRIPTION
+ * Function to set the descriptions and names for each process names group.
+ * Takes a config option containing list of strings that are used to set
+ * process group values.
+ *
+ * PARAMETERS
+ * `item' Config option containing process names groups.
+ * `groups' Table of process name groups to set values in.
+ * `max_groups' Maximum number of process name groups allowed.
+ *
+ * RETURN VALUE
+ * On success, the number of name groups set up. On error, appropriate
+ * negative error value.
+ */
+static int oconfig_to_ngroups(const oconfig_item_t *item,
+ rdt_name_group_t *groups,
+ const size_t max_groups) {
+ int index = 0;
+
+ assert(groups != NULL);
+ assert(max_groups > 0);
+ assert(item != NULL);
+
+ for (int j = 0; j < item->values_num; j++) {
+ int ret;
+ char value[DATA_MAX_NAME_LEN];
+
+ if ((item->values[j].value.string == NULL) ||
+ (strlen(item->values[j].value.string) == 0)) {
+ ERROR(RDT_PLUGIN ": Error - empty group");
+ return -EINVAL;
+ }
+
+ sstrncpy(value, item->values[j].value.string, sizeof(value));
+
+ ret = strlisttoarray(value, &groups[index].names, &groups[index].num_names);
+ if (ret != 0 || groups[index].num_names == 0) {
+ ERROR(RDT_PLUGIN ": Error parsing process names group (%s)",
+ item->values[j].value.string);
+ return -EINVAL;
+ }
+
+ /* set group description info */
+ groups[index].desc = sstrdup(item->values[j].value.string);
+ if (groups[index].desc == NULL) {
+ ERROR(RDT_PLUGIN ": Error allocating name group description");
+ return -ENOMEM;
+ }
+
+ groups[index].proc_pids = NULL;
+ groups[index].monitored_pids_count = 0;
+
+ index++;
+
+ if (index >= (const int)max_groups) {
+ WARNING(RDT_PLUGIN ": Too many process names groups configured");
+ return index;
+ }
+ }
+
+ return index;
+}
+
+/*
+ * NAME
+ * rdt_free_ngroups
+ *
+ * DESCRIPTION
+ * Function to deallocate memory allocated for name groups.
+ *
+ * PARAMETERS
+ * `rdt' Pointer to rdt context
+ */
+static void rdt_free_ngroups(rdt_ctx_t *rdt) {
+ for (int i = 0; i < RDT_MAX_NAMES_GROUPS; i++) {
+ if (rdt->ngroups[i].desc)
+ DEBUG(RDT_PLUGIN ": Freeing pids \'%s\' group\'s data...",
+ rdt->ngroups[i].desc);
+ sfree(rdt->ngroups[i].desc);
+ strarray_free(rdt->ngroups[i].names, rdt->ngroups[i].num_names);
+
+ if (rdt->ngroups[i].proc_pids)
+ proc_pids_free(rdt->ngroups[i].proc_pids, rdt->ngroups[i].num_names);
+
+ rdt->ngroups[i].num_names = 0;
+ sfree(rdt->pngroups[i]);
+ }
+ if (rdt->proc_pids)
+ sfree(rdt->proc_pids);
+
+ rdt->num_ngroups = 0;
+}
+
+/*
+ * NAME
+ * rdt_config_ngroups
+ *
+ * DESCRIPTION
+ * Reads name groups configuration.
+ *
+ * PARAMETERS
+ * `rdt` Pointer to rdt context
+ * `item' Config option containing process names groups.
+ *
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+static int rdt_config_ngroups(rdt_ctx_t *rdt, const oconfig_item_t *item) {
+ int n = 0;
+ enum pqos_mon_event events = 0;
+
+ if (item == NULL) {
+ DEBUG(RDT_PLUGIN ": ngroups_config: Invalid argument.");
+ return -EINVAL;
+ }
+
+ DEBUG(RDT_PLUGIN ": Process names groups [%d]:", item->values_num);
+ for (int j = 0; j < item->values_num; j++) {
+ if (item->values[j].type != OCONFIG_TYPE_STRING) {
+ ERROR(RDT_PLUGIN
+ ": given process names group value is not a string [idx=%d]",
+ j);
+ return -EINVAL;
+ }
+ DEBUG(RDT_PLUGIN ": [%d]: %s", j, item->values[j].value.string);
+ }
+
+ n = oconfig_to_ngroups(item, rdt->ngroups, RDT_MAX_NAMES_GROUPS);
+ if (n < 0) {
+ rdt_free_ngroups(rdt);
+ ERROR(RDT_PLUGIN ": Error parsing process name groups configuration.");
+ return -EINVAL;
+ }
+
+ /* validate configured process name values */
+ for (int group_idx = 0; group_idx < n; group_idx++) {
+ DEBUG(RDT_PLUGIN ": checking group [%d]: %s", group_idx,
+ rdt->ngroups[group_idx].desc);
+ for (size_t name_idx = 0; name_idx < rdt->ngroups[group_idx].num_names;
+ name_idx++) {
+ DEBUG(RDT_PLUGIN ": checking process name [%zu]: %s", name_idx,
+ rdt->ngroups[group_idx].names[name_idx]);
+ if (!proc_pids_is_name_valid(rdt->ngroups[group_idx].names[name_idx])) {
+ ERROR(RDT_PLUGIN ": Process name group '%s' contains invalid name '%s'",
+ rdt->ngroups[group_idx].desc,
+ rdt->ngroups[group_idx].names[name_idx]);
+ rdt_free_ngroups(rdt);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (n == 0) {
+ ERROR(RDT_PLUGIN ": Empty process name groups configured.");
+ return -EINVAL;
+ }
+
+ /* Get all available events on this platform */
+ for (unsigned i = 0; i < rdt->cap_mon->u.mon->num_events; i++)
+ events |= rdt->cap_mon->u.mon->events[i].type;
+
+ events &= ~(PQOS_PERF_EVENT_LLC_MISS);
+
+ DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
+
+ rdt->num_ngroups = n;
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < i; j++) {
+ int found = ngroup_cmp(&rdt->ngroups[j], &rdt->ngroups[i]);
+ if (found != 0) {
+ rdt_free_ngroups(rdt);
+ ERROR(RDT_PLUGIN
+ ": Cannot monitor same process name in different groups.");
+ return -EINVAL;
+ }
+ }
+
+ rdt->ngroups[i].events = events;
+ rdt->pngroups[i] = calloc(1, sizeof(*rdt->pngroups[i]));
+ if (rdt->pngroups[i] == NULL) {
+ rdt_free_ngroups(rdt);
+ ERROR(RDT_PLUGIN
+ ": Failed to allocate memory for process name monitoring data.");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * NAME
+ * rdt_refresh_ngroup
+ *
+ * DESCRIPTION
+ * Refresh pids monitored by name group.
+ *
+ * PARAMETERS
+ * `ngroup` Pointer to name group.
+ * `group_mon_data' PQoS monitoring context.
+ *
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+static int rdt_refresh_ngroup(rdt_name_group_t *ngroup,
+ struct pqos_mon_data *group_mon_data) {
+
+ int result = 0;
+
+ if (NULL == ngroup)
+ return -1;
+
+ if (NULL == ngroup->proc_pids) {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\' uninitialized process pids array.",
+ ngroup->desc);
+
+ return -1;
+ }
+
+ DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group.",
+ ngroup->desc);
+
+ proc_pids_t **proc_pids = ngroup->proc_pids;
+ pids_list_t added_pids;
+ pids_list_t removed_pids;
+
+ memset(&added_pids, 0, sizeof(added_pids));
+ memset(&removed_pids, 0, sizeof(removed_pids));
+
+ for (size_t i = 0; i < ngroup->num_names; ++i) {
+ int diff_result = pids_list_diff(proc_pids[i], &added_pids, &removed_pids);
+ if (0 != diff_result) {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\'. Error [%d] during PID diff.",
+ ngroup->desc, diff_result);
+ result = -1;
+ goto cleanup;
+ }
+ }
+
+ DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group, added: "
+ "%u, removed: %u.",
+ ngroup->desc, (unsigned)added_pids.size, (unsigned)removed_pids.size);
+
+ if (added_pids.size > 0) {
+
+ /* no pids are monitored for this group yet: start monitoring */
+ if (0 == ngroup->monitored_pids_count) {
+
+ int start_result =
+ pqos_mon_start_pids(added_pids.size, added_pids.pids, ngroup->events,
+ (void *)ngroup->desc, group_mon_data);
+ if (PQOS_RETVAL_OK == start_result) {
+ ngroup->monitored_pids_count = added_pids.size;
+ } else {
+ ERROR(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\'. Error [%d] while "
+ "STARTING pids monitoring",
+ ngroup->desc, start_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+
+ } else {
+
+ int add_result =
+ pqos_mon_add_pids(added_pids.size, added_pids.pids, group_mon_data);
+ if (PQOS_RETVAL_OK == add_result)
+ ngroup->monitored_pids_count += added_pids.size;
+ else {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\'. Error [%d] while ADDING pids.",
+ ngroup->desc, add_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+ }
+ }
+
+ if (removed_pids.size > 0) {
+
+ /* all pids are removed: stop monitoring */
+ if (removed_pids.size == ngroup->monitored_pids_count) {
+ /* all pids for this group are lost: stop monitoring */
+ int stop_result = pqos_mon_stop(group_mon_data);
+ if (PQOS_RETVAL_OK != stop_result) {
+ ERROR(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\'. Error [%d] while "
+ "STOPPING monitoring",
+ ngroup->desc, stop_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+ ngroup->monitored_pids_count = 0;
+ } else {
+ int remove_result = pqos_mon_remove_pids(
+ removed_pids.size, removed_pids.pids, group_mon_data);
+ if (PQOS_RETVAL_OK == remove_result) {
+ ngroup->monitored_pids_count -= removed_pids.size;
+ } else {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\'. Error [%d] while REMOVING pids.",
+ ngroup->desc, remove_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+ }
}
+
+ goto cleanup;
+
+pqos_error_recovery:
+ /* Why?
+ * Resources might be temporary unavailable.
+ *
+ * How?
+ * Collectd will halt the reading thread for this
+ * plugin if it returns an error.
+ * Consecutive errors will be increasing the read period
+ * up to 1 day interval.
+ * On pqos error stop monitoring current group
+ * and reset the proc_pids array
+ * monitoring will be restarted on next collectd read cycle
+ */
+ DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' group RESET after error.",
+ ngroup->desc);
+ pqos_mon_stop(group_mon_data);
+ for (size_t i = 0; i < ngroup->num_names; ++i)
+ if (ngroup->proc_pids[i]->curr)
+ ngroup->proc_pids[i]->curr->size = 0;
+
+ ngroup->monitored_pids_count = 0;
+
+cleanup:
+ pids_list_clear(&added_pids);
+ pids_list_clear(&removed_pids);
+
+ return result;
}
+
+/*
+ * NAME
+ * read_pids_data
+ *
+ * DESCRIPTION
+ * Poll monitoring statistics for name groups
+ *
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+static int read_pids_data() {
+
+ if (0 == g_rdt->num_ngroups) {
+ DEBUG(RDT_PLUGIN ": read_pids_data: not configured - PIDs read skipped");
+ return 0;
+ }
+
+ DEBUG(RDT_PLUGIN ": read_pids_data: Scanning active groups");
+ struct pqos_mon_data *active_groups[RDT_MAX_NAMES_GROUPS] = {0};
+ size_t active_group_idx = 0;
+ for (size_t pngroups_idx = 0;
+ pngroups_idx < STATIC_ARRAY_SIZE(g_rdt->pngroups); ++pngroups_idx)
+ if (0 != g_rdt->ngroups[pngroups_idx].monitored_pids_count)
+ active_groups[active_group_idx++] = g_rdt->pngroups[pngroups_idx];
+
+ int ret = 0;
+
+ if (0 == active_group_idx) {
+ DEBUG(RDT_PLUGIN ": read_pids_data: no active groups - PIDs read skipped");
+ goto groups_refresh;
+ }
+
+ DEBUG(RDT_PLUGIN ": read_pids_data: PIDs data polling");
+
+ int poll_result = pqos_mon_poll(active_groups, active_group_idx);
+ if (poll_result != PQOS_RETVAL_OK) {
+ ERROR(RDT_PLUGIN ": read_pids_data: Failed to poll monitoring data for "
+ "pids. Error [%d].",
+ poll_result);
+ ret = -poll_result;
+ goto groups_refresh;
+ }
+
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+ enum pqos_mon_event mbm_events =
+ (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
+ PQOS_MON_EVENT_RMEM_BW);
+
+ if (g_rdt->pngroups[i] == NULL ||
+ g_rdt->ngroups[i].monitored_pids_count == 0)
+ continue;
+
+ const struct pqos_event_values *pv = &g_rdt->pngroups[i]->values;
+
+ /* Submit only monitored events data */
+
+ if (g_rdt->ngroups[i].events & PQOS_MON_EVENT_L3_OCCUP)
+ rdt_submit_gauge(g_rdt->ngroups[i].desc, "bytes", "llc", pv->llc);
+
+ if (g_rdt->ngroups[i].events & PQOS_PERF_EVENT_IPC)
+ rdt_submit_gauge(g_rdt->ngroups[i].desc, "ipc", NULL, pv->ipc);
+
+ if (g_rdt->ngroups[i].events & mbm_events) {
+ rdt_submit_derive(g_rdt->ngroups[i].desc, "memory_bandwidth", "local",
+ pv->mbm_local_delta);
+ rdt_submit_derive(g_rdt->ngroups[i].desc, "memory_bandwidth", "remote",
+ pv->mbm_remote_delta);
+ }
+ }
+
+#if COLLECT_DEBUG
+ rdt_dump_pids_data();
#endif /* COLLECT_DEBUG */
+groups_refresh:
+ ret = proc_pids_update(RDT_PROC_PATH, g_rdt->proc_pids, g_rdt->num_proc_pids);
+ if (0 != ret) {
+ ERROR(RDT_PLUGIN ": Initial update of proc pids failed");
+ return ret;
+ }
+
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+ int refresh_result =
+ rdt_refresh_ngroup(&(g_rdt->ngroups[i]), g_rdt->pngroups[i]);
+
+ if (0 != refresh_result) {
+ ERROR(RDT_PLUGIN ": read_pids_data: NGroup %zu refresh failed. Error: %d",
+ i, refresh_result);
+ if (0 == ret) {
+ /* refresh error will be escalated only if there were no
+ * errors before.
+ */
+ ret = refresh_result;
+ }
+ }
+ }
+
+ assert(ret <= 0);
+ return ret;
+}
+
+/*
+ * NAME
+ * rdt_init_pids_monitoring
+ *
+ * DESCRIPTION
+ * Initialize pids monitoring for all name groups
+ */
+static void rdt_init_pids_monitoring() {
+ for (size_t group_idx = 0; group_idx < g_rdt->num_ngroups; group_idx++) {
+ /*
+ * Each group must have not-null proc_pids array.
+ * Initial refresh is not mandatory for proper
+ * PIDs statistics detection.
+ */
+ rdt_name_group_t *ng = &g_rdt->ngroups[group_idx];
+ int init_result =
+ proc_pids_init((const char **)ng->names, ng->num_names, &ng->proc_pids);
+ if (0 != init_result) {
+ ERROR(RDT_PLUGIN
+ ": Initialization of proc_pids for group %zu failed. Error: %d",
+ group_idx, init_result);
+ continue;
+ }
+
+ /* update global proc_pids table */
+ proc_pids_t **proc_pids = realloc(g_rdt->proc_pids,
+ (g_rdt->num_proc_pids + ng->num_names) *
+ sizeof(*g_rdt->proc_pids));
+ if (NULL == proc_pids) {
+ ERROR(RDT_PLUGIN ": Alloc error\n");
+ continue;
+ }
+
+ for (size_t i = 0; i < ng->num_names; i++)
+ proc_pids[g_rdt->num_proc_pids + i] = ng->proc_pids[i];
+
+ g_rdt->proc_pids = proc_pids;
+ g_rdt->num_proc_pids += ng->num_names;
+ }
+
+ if (g_rdt->num_ngroups > 0) {
+ int update_result =
+ proc_pids_update(RDT_PROC_PATH, g_rdt->proc_pids, g_rdt->num_proc_pids);
+ if (0 != update_result)
+ ERROR(RDT_PLUGIN ": Initial update of proc pids failed");
+ }
+
+ for (size_t group_idx = 0; group_idx < g_rdt->num_ngroups; group_idx++) {
+ int refresh_result = rdt_refresh_ngroup(&(g_rdt->ngroups[group_idx]),
+ g_rdt->pngroups[group_idx]);
+ if (0 != refresh_result)
+ ERROR(RDT_PLUGIN ": Initial refresh of group %zu failed. Error: %d",
+ group_idx, refresh_result);
+ }
+}
+#endif /* LIBPQOS2 */
+/*
+ * NAME
+ * rdt_free_cgroups
+ *
+ * DESCRIPTION
+ * Function to deallocate memory allocated for core groups.
+ */
static void rdt_free_cgroups(void) {
config_cores_cleanup(&g_rdt->cores);
for (int i = 0; i < RDT_MAX_CORES; i++) {
- sfree(g_rdt->pgroups[i]);
+ sfree(g_rdt->pcgroups[i]);
}
+ g_rdt->cores.num_cgroups = 0;
}
static int rdt_default_cgroups(void) {
unsigned num_cores = g_rdt->pqos_cpu->num_cores;
- g_rdt->cores.cgroups = calloc(num_cores, sizeof(*g_rdt->cores.cgroups));
+ g_rdt->cores.cgroups = calloc(num_cores, sizeof(*(g_rdt->cores.cgroups)));
if (g_rdt->cores.cgroups == NULL) {
ERROR(RDT_PLUGIN ": Error allocating core groups array");
return -ENOMEM;
g_rdt->pqos_cpu->num_cores);
DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
- g_rdt->num_groups = n;
- for (size_t i = 0; i < n; i++) {
- for (size_t j = 0; j < i; j++) {
+ g_rdt->cores.num_cgroups = n;
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < i; j++) {
int found = 0;
found = config_cores_cmp_cgroups(&g_rdt->cores.cgroups[j],
&g_rdt->cores.cgroups[i]);
}
g_rdt->events[i] = events;
- g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
- if (g_rdt->pgroups[i] == NULL) {
+ g_rdt->pcgroups[i] = calloc(1, sizeof(*g_rdt->pcgroups[i]));
+ if (g_rdt->pcgroups[i] == NULL) {
rdt_free_cgroups();
ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
return -ENOMEM;
struct pqos_config pqos = {.fd_log = -1,
.callback_log = rdt_pqos_log,
.context_log = NULL,
- .verbose = 0};
+ .verbose = 0,
+#ifdef LIBPQOS2
+ .interface = PQOS_INTER_OS_RESCTRL_MON};
+ DEBUG(RDT_PLUGIN ": Initializing PQoS with RESCTRL interface");
+#else
+ .interface = PQOS_INTER_MSR};
+ DEBUG(RDT_PLUGIN ": Initializing PQoS with MSR interface");
+#endif
ret = pqos_init(&pqos);
+ DEBUG(RDT_PLUGIN ": PQoS initialization result: [%d]", ret);
+
+#ifdef LIBPQOS2
+ if (ret == PQOS_RETVAL_INTER) {
+ pqos.interface = PQOS_INTER_MSR;
+ DEBUG(RDT_PLUGIN ": Initializing PQoS with MSR interface");
+ ret = pqos_init(&pqos);
+ DEBUG(RDT_PLUGIN ": PQoS initialization result: [%d]", ret);
+ }
+#endif
+
if (ret != PQOS_RETVAL_OK) {
ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
goto rdt_preinit_error1;
}
+ g_interface = pqos.interface;
+
ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
if (ret != PQOS_RETVAL_OK) {
ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
pqos_fini();
rdt_preinit_error1:
-
sfree(g_rdt);
return -1;
reports a failure in configuration and
aborts
*/
- return (0);
+ return 0;
}
for (int i = 0; i < ci->children_num; i++) {
oconfig_item_t *child = ci->children + i;
- if (strcasecmp("Cores", child->key) == 0) {
- if (rdt_config_cgroups(child) != 0) {
+ if (strncasecmp("Cores", child->key, (size_t)strlen("Cores")) == 0) {
+ if (g_rdt->cores.num_cgroups > 0) {
+ ERROR(RDT_PLUGIN
+ ": Configuration parameter \"%s\" can be used only once.",
+ child->key);
g_state = CONFIGURATION_ERROR;
+ } else if (rdt_config_cgroups(child) != 0)
+ g_state = CONFIGURATION_ERROR;
+
+ if (g_state == CONFIGURATION_ERROR)
/* if we return -1 at this point collectd
reports a failure in configuration and
aborts
*/
- return (0);
- }
+ return 0;
#if COLLECT_DEBUG
rdt_dump_cgroups();
#endif /* COLLECT_DEBUG */
+ } else if (strncasecmp("Processes", child->key,
+ (size_t)strlen("Processes")) == 0) {
+#ifdef LIBPQOS2
+ if (g_interface != PQOS_INTER_OS_RESCTRL_MON) {
+ ERROR(RDT_PLUGIN ": Configuration parameter \"%s\" not supported. "
+ "Resctrl monitoring is needed for PIDs monitoring.",
+ child->key);
+ g_state = CONFIGURATION_ERROR;
+ }
+
+ else if (g_rdt->num_ngroups > 0) {
+ ERROR(RDT_PLUGIN
+ ": Configuration parameter \"%s\" can be used only once.",
+ child->key);
+ g_state = CONFIGURATION_ERROR;
+ }
+
+ else if (rdt_config_ngroups(g_rdt, child) != 0)
+ g_state = CONFIGURATION_ERROR;
+
+ if (g_state == CONFIGURATION_ERROR)
+ /* if we return -1 at this point collectd
+ reports a failure in configuration and
+ aborts
+ */
+ return 0;
+
+#if COLLECT_DEBUG
+ rdt_dump_ngroups();
+#endif /* COLLECT_DEBUG */
+#else /* !LIBPQOS2 */
+ ERROR(RDT_PLUGIN ": Configuration parameter \"%s\" not supported, please "
+ "recompile collectd with libpqos version 2.0 or newer.",
+ child->key);
+#endif /* LIBPQOS2 */
} else {
ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
}
return 0;
}
-static void rdt_submit_derive(const char *cgroup, const char *type,
- const char *type_instance, derive_t value) {
- value_list_t vl = VALUE_LIST_INIT;
-
- vl.values = &(value_t){.derive = value};
- vl.values_len = 1;
-
- sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
- snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
- sstrncpy(vl.type, type, sizeof(vl.type));
- if (type_instance)
- sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
-
- plugin_dispatch_values(&vl);
-}
-
-static void rdt_submit_gauge(const char *cgroup, const char *type,
- const char *type_instance, gauge_t value) {
- value_list_t vl = VALUE_LIST_INIT;
-
- vl.values = &(value_t){.gauge = value};
- vl.values_len = 1;
-
- sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
- snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
- sstrncpy(vl.type, type, sizeof(vl.type));
- if (type_instance)
- sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
-
- plugin_dispatch_values(&vl);
-}
-
-static int rdt_read(__attribute__((unused)) user_data_t *ud) {
- int ret;
+static int read_cores_data() {
- if (g_rdt == NULL) {
- ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
- return -EINVAL;
+ if (0 == g_rdt->cores.num_cgroups) {
+ DEBUG(RDT_PLUGIN ": read_cores_data: not configured - Cores read skipped");
+ return 0;
}
+ DEBUG(RDT_PLUGIN ": read_cores_data: Cores data poll");
- ret = pqos_mon_poll(&g_rdt->pgroups[0], (unsigned)g_rdt->num_groups);
+ int ret =
+ pqos_mon_poll(&g_rdt->pcgroups[0], (unsigned)g_rdt->cores.num_cgroups);
if (ret != PQOS_RETVAL_OK) {
- ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
+ ERROR(RDT_PLUGIN ": read_cores_data: Failed to poll monitoring data for "
+ "cores. Error [%d].",
+ ret);
return -1;
}
-#if COLLECT_DEBUG
- rdt_dump_data();
-#endif /* COLLECT_DEBUG */
-
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
core_group_t *cgroup = g_rdt->cores.cgroups + i;
-
enum pqos_mon_event mbm_events =
(PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
PQOS_MON_EVENT_RMEM_BW);
- const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+ const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
/* Submit only monitored events data */
}
}
+#if COLLECT_DEBUG
+ rdt_dump_cores_data();
+#endif /* COLLECT_DEBUG */
+
return 0;
}
-static int rdt_init(void) {
- int ret;
+static int rdt_read(__attribute__((unused)) user_data_t *ud) {
- if (g_state == CONFIGURATION_ERROR)
- return -1;
+ if (g_rdt == NULL) {
+ ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
+ return -EINVAL;
+ }
- ret = rdt_preinit();
- if (ret != 0)
- return ret;
+ int cores_read_result = read_cores_data();
+
+#ifdef LIBPQOS2
+ int pids_read_result = read_pids_data();
+#endif /* LIBPQOS2 */
+
+ if (0 != cores_read_result)
+ return cores_read_result;
- /* Start monitoring */
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
+#ifdef LIBPQOS2
+ if (0 != pids_read_result)
+ return pids_read_result;
+#endif /* LIBPQOS2 */
+
+ return 0;
+}
+
+static void rdt_init_cores_monitoring() {
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
core_group_t *cg = g_rdt->cores.cgroups + i;
- ret = pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
- (void *)cg->desc, g_rdt->pgroups[i]);
+ int mon_start_result =
+ pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
+ (void *)cg->desc, g_rdt->pcgroups[i]);
+
+ if (mon_start_result != PQOS_RETVAL_OK)
+ ERROR(RDT_PLUGIN
+ ": Error starting cores monitoring group %s (pqos status=%d)",
+ cg->desc, mon_start_result);
+ }
+}
+
+static int rdt_init(void) {
- if (ret != PQOS_RETVAL_OK)
- ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
- cg->desc, ret);
+ if (g_state == CONFIGURATION_ERROR) {
+ if (g_rdt != NULL) {
+ if (g_rdt->cores.num_cgroups > 0)
+ rdt_free_cgroups();
+#ifdef LIBPQOS2
+ if (g_rdt->num_ngroups > 0)
+ rdt_free_ngroups(g_rdt);
+#endif
+ }
+ return -1;
}
+ int rdt_preinint_result = rdt_preinit();
+ if (rdt_preinint_result != 0)
+ return rdt_preinint_result;
+
+ rdt_init_cores_monitoring();
+#ifdef LIBPQOS2
+ rdt_init_pids_monitoring();
+#endif /* LIBPQOS2 */
+
return 0;
}
if (g_rdt == NULL)
return 0;
- /* Stop monitoring */
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
- pqos_mon_stop(g_rdt->pgroups[i]);
+ /* Stop monitoring cores */
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
+ pqos_mon_stop(g_rdt->pcgroups[i]);
}
+/* Stop pids monitoring */
+#ifdef LIBPQOS2
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++)
+ pqos_mon_stop(g_rdt->pngroups[i]);
+#endif
+
ret = pqos_fini();
if (ret != PQOS_RETVAL_OK)
ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
-
rdt_free_cgroups();
+#ifdef LIBPQOS2
+ rdt_free_ngroups(g_rdt);
+#endif /* LIBPQOS2 */
sfree(g_rdt);
return 0;
--- /dev/null
+#include "intel_rdt.c" /* sic */
+#include "testing.h"
+
+/***************************************************************************
+ * PQOS mocks
+ */
+int pqos_mon_reset(void) { return 0; }
+int pqos_mon_assoc_get(const unsigned lcore, pqos_rmid_t *rmid) { return 0; }
+int pqos_mon_start(const unsigned num_cores, const unsigned *cores,
+ const enum pqos_mon_event event, void *context,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+#if PQOS_VERSION >= 30000
+int pqos_mon_start_pids(const unsigned num_pids, const pid_t *pids,
+ const enum pqos_mon_event event, void *context,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+int pqos_mon_add_pids(const unsigned num_pids, const pid_t *pids,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+int pqos_mon_remove_pids(const unsigned num_pids, const pid_t *pids,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+
+#else
+int pqos_mon_start_pid(const pid_t pids, const enum pqos_mon_event event,
+ void *context, struct pqos_mon_data *group) {
+ return 0;
+}
+#endif
+int pqos_mon_stop(struct pqos_mon_data *group) { return 0; }
+int pqos_mon_poll(struct pqos_mon_data **groups, const unsigned num_groups) {
+ return 0;
+}
+
+#if PQOS_VERSION >= 30000
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg,
+ const enum pqos_cdp_config l2_cdp_cfg,
+ const enum pqos_mba_config mba_cfg) {
+ return 0;
+}
+#elif PQOS_VERSION >= 20000
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg,
+ const enum pqos_cdp_config l2_cdp_cfg) {
+ return 0;
+}
+#else
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg) { return 0; }
+#endif
+int pqos_alloc_assoc_set(const unsigned lcore, const unsigned class_id) {
+ return 0;
+}
+int pqos_alloc_assoc_get(const unsigned lcore, unsigned *class_id) { return 0; }
+int pqos_alloc_assoc_set_pid(const pid_t task, const unsigned class_id) {
+ return 0;
+}
+int pqos_alloc_assoc_get_pid(const pid_t task, unsigned *class_id) { return 0; }
+int pqos_alloc_assign(const unsigned technology, const unsigned *core_array,
+ const unsigned core_num, unsigned *class_id) {
+ return 0;
+}
+int pqos_alloc_release(const unsigned *core_array, const unsigned core_num) {
+ return 0;
+}
+int pqos_alloc_assign_pid(const unsigned technology, const pid_t *task_array,
+ const unsigned task_num, unsigned *class_id) {
+ return 0;
+}
+int pqos_alloc_release_pid(const pid_t *task_array, const unsigned task_num) {
+ return 0;
+}
+int pqos_init(const struct pqos_config *config) { return 0; }
+int pqos_fini(void) { return 0; }
+int pqos_cap_get_type(const struct pqos_cap *cap, const enum pqos_cap_type type,
+ const struct pqos_capability **cap_item) {
+ return 0;
+}
+int pqos_cap_get(const struct pqos_cap **cap, const struct pqos_cpuinfo **cpu) {
+ return 0;
+}
+
+#ifdef LIBPQOS2
+/***************************************************************************
+ * helper functions
+ */
+rdt_ctx_t *stub_rdt_setup() {
+
+ rdt_ctx_t *rdt = calloc(1, sizeof(*rdt));
+ struct pqos_cpuinfo *pqos_cpu = calloc(1, sizeof(*pqos_cpu));
+ struct pqos_cap *pqos_cap = calloc(1, sizeof(*pqos_cap));
+ struct pqos_cap_mon *mon = calloc(1, sizeof(*mon));
+ struct pqos_capability *cap_mon = calloc(1, sizeof(*cap_mon));
+
+ cap_mon->u.mon = mon;
+ rdt->pqos_cap = pqos_cap;
+ rdt->pqos_cpu = pqos_cpu;
+ rdt->cap_mon = cap_mon;
+
+ return rdt;
+}
+
+void stub_rdt_teardown(rdt_ctx_t *rdt) {
+ free(rdt->cap_mon->u.mon);
+ free((void *)rdt->cap_mon);
+ free((void *)rdt->pqos_cpu);
+ free((void *)rdt->pqos_cap);
+ free(rdt);
+}
+
+/***************************************************************************
+ * tests
+ */
+DEF_TEST(rdt_config_ngroups__one_process) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc1", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_STR(values[0].value.string, rdt->ngroups[0].desc);
+ EXPECT_EQ_INT(1, rdt->num_ngroups);
+
+ /* cleanup */
+ rdt_free_ngroups(rdt);
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__two_groups) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc12,proc13", .type = OCONFIG_TYPE_STRING},
+ {.value.string = "proc21,proc22,proc23", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(2, rdt->num_ngroups);
+ EXPECT_EQ_STR("proc11,proc12,proc13", rdt->ngroups[0].desc);
+ EXPECT_EQ_STR("proc21,proc22,proc23", rdt->ngroups[1].desc);
+ EXPECT_EQ_STR("proc11", rdt->ngroups[0].names[0]);
+ EXPECT_EQ_STR("proc12", rdt->ngroups[0].names[1]);
+ EXPECT_EQ_STR("proc13", rdt->ngroups[0].names[2]);
+ EXPECT_EQ_STR("proc21", rdt->ngroups[1].names[0]);
+ EXPECT_EQ_STR("proc22", rdt->ngroups[1].names[1]);
+ EXPECT_EQ_STR("proc23", rdt->ngroups[1].names[2]);
+
+ /* cleanup */
+ rdt_free_ngroups(rdt);
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__too_long_proc_name) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "_seventeen_chars_", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__duplicate_proc_name_between_groups) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc12,proc", .type = OCONFIG_TYPE_STRING},
+ {.value.string = "proc21,proc,proc23", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__duplicate_proc_name_in_group) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc,proc,proc14", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__empty_group) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc12,proc13", .type = OCONFIG_TYPE_STRING},
+ {.value.string = "", .type = OCONFIG_TYPE_STRING},
+
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__empty_proc_name) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,,proc13", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+int main(void) {
+ RUN_TEST(rdt_config_ngroups__one_process);
+ RUN_TEST(rdt_config_ngroups__two_groups);
+ RUN_TEST(rdt_config_ngroups__too_long_proc_name);
+ RUN_TEST(rdt_config_ngroups__duplicate_proc_name_between_groups);
+ RUN_TEST(rdt_config_ngroups__duplicate_proc_name_in_group);
+ RUN_TEST(rdt_config_ngroups__empty_group);
+ RUN_TEST(rdt_config_ngroups__empty_proc_name);
+ END_TEST;
+}
+
+#else
+DEF_TEST(pqos12_test_stub) {
+ EXPECT_EQ_INT(0, 0);
+ return 0;
+}
+
+int main(void) {
+ RUN_TEST(pqos12_test_stub);
+ END_TEST;
+}
+#endif
#include <pthread.h>
+#define PLUGIN_READ 1
+#define PLUGIN_WRITE 2
+
typedef struct lua_script_s {
- char *script_path;
lua_State *lua_state;
struct lua_script_s *next;
} lua_script_t;
* garbage collector. */
static int clua_store_thread(lua_State *L, int idx) /* {{{ */
{
- if (idx < 0)
- idx += lua_gettop(L) + 1;
-
- /* Copy the thread pointer */
- lua_pushvalue(L, idx); /* +1 = 3 */
- if (!lua_isthread(L, -1)) {
- lua_pop(L, 3); /* -3 = 0 */
+ if (!lua_isthread(L, idx)) {
return -1;
}
+ /* Copy the thread pointer */
+ lua_pushvalue(L, idx);
+
luaL_ref(L, LUA_REGISTRYINDEX);
- lua_pop(L, 1); /* -1 = 0 */
return 0;
} /* }}} int clua_store_thread */
static void lua_cb_free(void *data) {
clua_callback_data_t *cb = data;
free(cb->lua_function_name);
+ pthread_mutex_destroy(&cb->lock);
free(cb);
}
-static int lua_cb_register_read(lua_State *L) /* {{{ */
+static int lua_cb_register_generic(lua_State *L, int type) /* {{{ */
{
int nargs = lua_gettop(L);
if (nargs != 1)
return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
- luaL_checktype(L, 1, LUA_TFUNCTION);
-
- char function_name[DATA_MAX_NAME_LEN];
- snprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
-
- int callback_id = clua_store_callback(L, 1);
- if (callback_id < 0)
- return luaL_error(L, "%s", "Storing callback function failed");
-
- lua_State *thread = lua_newthread(L);
- if (thread == NULL)
- return luaL_error(L, "%s", "lua_newthread failed");
- clua_store_thread(L, -1);
- lua_pop(L, 1);
-
- clua_callback_data_t *cb = calloc(1, sizeof(*cb));
- if (cb == NULL)
- return luaL_error(L, "%s", "calloc failed");
-
- cb->lua_state = thread;
- cb->callback_id = callback_id;
- cb->lua_function_name = strdup(function_name);
- pthread_mutex_init(&cb->lock, NULL);
-
- int status =
- plugin_register_complex_read(/* group = */ "lua",
- /* name = */ function_name,
- /* callback = */ clua_read,
- /* interval = */ 0,
- &(user_data_t){
- .data = cb, .free_func = lua_cb_free,
- });
-
- if (status != 0)
- return luaL_error(L, "%s", "plugin_register_complex_read failed");
- return 0;
-} /* }}} int lua_cb_register_read */
-
-static int lua_cb_register_write(lua_State *L) /* {{{ */
-{
- int nargs = lua_gettop(L);
+ char subname[DATA_MAX_NAME_LEN];
+ if (!lua_isfunction(L, 1) && lua_isstring(L, 1)) {
+ const char *fname = lua_tostring(L, 1);
+ snprintf(subname, sizeof(subname), "%s()", fname);
- if (nargs != 1)
- return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
+ lua_getglobal(L, fname); // Push function into stack
+ lua_remove(L, 1); // Remove string from stack
+ if (!lua_isfunction(L, -1)) {
+ return luaL_error(L, "Unable to find function '%s'", fname);
+ }
+ } else {
+ lua_getfield(L, LUA_REGISTRYINDEX, "collectd:callback_num");
+ int tmp = lua_tointeger(L, -1);
+ snprintf(subname, sizeof(subname), "callback_%d", tmp);
+ lua_pop(L, 1); // Remove old value from stack
+ lua_pushinteger(L, tmp + 1);
+ lua_setfield(L, LUA_REGISTRYINDEX, "collectd:callback_num"); // pops value
+ }
luaL_checktype(L, 1, LUA_TFUNCTION);
- char function_name[DATA_MAX_NAME_LEN] = "";
- snprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
+ lua_getfield(L, LUA_REGISTRYINDEX, "collectd:script_path");
+ char function_name[DATA_MAX_NAME_LEN];
+ snprintf(function_name, sizeof(function_name), "lua/%s/%s",
+ lua_tostring(L, -1), subname);
+ lua_pop(L, 1);
int callback_id = clua_store_callback(L, 1);
if (callback_id < 0)
cb->lua_function_name = strdup(function_name);
pthread_mutex_init(&cb->lock, NULL);
- int status = plugin_register_write(/* name = */ function_name,
- /* callback = */ clua_write,
+ if (PLUGIN_READ == type) {
+ int status =
+ plugin_register_complex_read(/* group = */ "lua",
+ /* name = */ function_name,
+ /* callback = */ clua_read,
+ /* interval = */ 0,
&(user_data_t){
.data = cb, .free_func = lua_cb_free,
});
- if (status != 0)
- return luaL_error(L, "%s", "plugin_register_write failed");
- return 0;
-} /* }}} int lua_cb_register_write */
+ if (status != 0)
+ return luaL_error(L, "%s", "plugin_register_complex_read failed");
+ return 0;
+ } else if (PLUGIN_WRITE == type) {
+ int status = plugin_register_write(/* name = */ function_name,
+ /* callback = */ clua_write,
+ &(user_data_t){
+ .data = cb, .free_func = lua_cb_free,
+ });
+
+ if (status != 0)
+ return luaL_error(L, "%s", "plugin_register_write failed");
+ return 0;
+ } else {
+ return luaL_error(L, "%s", "lua_cb_register_generic unsupported type");
+ }
+} /* }}} int lua_cb_register_generic */
+
+static int lua_cb_register_read(lua_State *L) {
+ return lua_cb_register_generic(L, PLUGIN_READ);
+}
+
+static int lua_cb_register_write(lua_State *L) {
+ return lua_cb_register_generic(L, PLUGIN_WRITE);
+}
static const luaL_Reg collectdlib[] = {
{"log_debug", lua_cb_log_debug},
script->lua_state = NULL;
}
- sfree(script->script_path);
sfree(script);
lua_script_free(next);
return status;
}
- script->script_path = strdup(script_path);
- if (script->script_path == NULL) {
- ERROR("Lua plugin: strdup failed.");
- lua_script_free(script);
- return -1;
- }
-
- status = luaL_loadfile(script->lua_state, script->script_path);
+ status = luaL_loadfile(script->lua_state, script_path);
if (status != 0) {
ERROR("Lua plugin: luaL_loadfile failed: %s",
lua_tostring(script->lua_state, -1));
return -1;
}
+ lua_pushstring(script->lua_state, script_path);
+ lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:script_path");
+ lua_pushinteger(script->lua_state, 0);
+ lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:callback_num");
+
status = lua_pcall(script->lua_state,
/* nargs = */ 0,
/* nresults = */ LUA_MULTRET,
"In addition, no error message could be retrieved from the stack.",
status);
else
- ERROR("Lua plugin: Executing script \"%s\" failed:\n%s",
- script->script_path, errmsg);
-
- lua_script_free(script);
- return -1;
+ ERROR("Lua plugin: Executing script \"%s\" failed: %s", script_path,
+ errmsg);
}
/* Append this script to the global list of scripts. */
scripts = script;
}
+ if (status != 0)
+ return -1;
+
return 0;
} /* }}} int lua_script_load */
MBCONN_RTU }; /* }}} */
typedef enum mb_conntype_e mb_conntype_t;
+enum mb_uarttype_e /* {{{ */
+{ UARTTYPE_RS232,
+ UARTTYPE_RS422,
+ UARTTYPE_RS485 }; /* }}} */
+typedef enum mb_uarttype_e mb_uarttype_t;
+
struct mb_data_s;
typedef struct mb_data_s mb_data_t;
struct mb_data_s /* {{{ */
char host[DATA_MAX_NAME_LEN];
char node[NI_MAXHOST]; /* TCP hostname or RTU serial device */
/* char service[NI_MAXSERV]; */
- int port; /* for Modbus/TCP */
- int baudrate; /* for Modbus/RTU */
+ int port; /* for Modbus/TCP */
+ int baudrate; /* for Modbus/RTU */
+ mb_uarttype_t uarttype; /* UART type for Modbus/RTU */
mb_conntype_t conntype;
mb_slave_t *slaves;
return status;
}
+#if defined(linux) && LIBMODBUS_VERSION_CHECK(2, 9, 4)
+ switch (host->uarttype) {
+ case UARTTYPE_RS485:
+ if (modbus_rtu_set_serial_mode(host->connection, MODBUS_RTU_RS485))
+ DEBUG("Modbus plugin: Setting RS485 mode failed.");
+ break;
+ case UARTTYPE_RS422:
+ /* libmodbus doesn't say anything about full-duplex symmetric RS422 UART */
+ break;
+ case UARTTYPE_RS232:
+ break;
+ default:
+ DEBUG("Modbus plugin: Invalid UART type!.");
+ }
+#endif /* defined(linux) && LIBMODBUS_VERSION_CHECK(2, 9, 4) */
+
return 0;
} /* }}} int mb_init_connection */
#endif /* !LEGACY_LIBMODBUS */
status = -1;
} else if (strcasecmp("Device", child->key) == 0) {
status = cf_util_get_string_buffer(child, host->node, sizeof(host->node));
- if (status == 0)
+ if (status == 0) {
host->conntype = MBCONN_RTU;
+ host->uarttype = UARTTYPE_RS232;
+ }
} else if (strcasecmp("Baudrate", child->key) == 0)
status = cf_util_get_int(child, &host->baudrate);
- else if (strcasecmp("Interval", child->key) == 0)
+ else if (strcasecmp("UARTType", child->key) == 0) {
+#if defined(linux) && !LEGACY_LIBMODBUS && LIBMODBUS_VERSION_CHECK(2, 9, 4)
+ char buffer[NI_MAXHOST];
+ status = cf_util_get_string_buffer(child, buffer, sizeof(buffer));
+ if (status != 0)
+ break;
+ if (strncmp(buffer, "RS485", 6) == 0)
+ host->uarttype = UARTTYPE_RS485;
+ else if (strncmp(buffer, "RS422", 6) == 0)
+ host->uarttype = UARTTYPE_RS422;
+ else if (strncmp(buffer, "RS232", 6) == 0)
+ host->uarttype = UARTTYPE_RS232;
+ else {
+ ERROR("Modbus plugin: The UARTType \"%s\" is unknown.", buffer);
+ status = -1;
+ break;
+ }
+#else
+ ERROR("Modbus plugin: Option `UARTType' not supported. Please "
+ "upgrade libmodbus to at least 2.9.4");
+ return -1;
+#endif
+ } else if (strcasecmp("Interval", child->key) == 0)
status = cf_util_get_cdtime(child, &interval);
else if (strcasecmp("Slave", child->key) == 0)
/* Don't set status: Gracefully continue if a slave fails. */
/* 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) {
+ for (int i = 0, j = len - 1; i < j; i++, j--) {
+ char 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) {
if (postfix == NULL)
postfix = "";
- gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char, 1);
+ if (flags & GRAPHITE_REVERSE_HOST) {
+ char r_host[DATA_MAX_NAME_LEN];
+ 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);
bool preserve_separator = (flags & GRAPHITE_PRESERVE_SEPARATOR);
- gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char,
- preserve_separator);
+ if (flags & GRAPHITE_REVERSE_HOST) {
+ char r_host[DATA_MAX_NAME_LEN];
+ 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,
#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,
--- /dev/null
+/**
+ * collectd - src/utils/proc_pids/proc_pids.c
+ *
+ * Copyright(c) 2018-2019 Intel Corporation. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ * Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ * Michał Aleksiński <michalx.aleksinski@intel.com>
+ **/
+
+#include "collectd.h"
+#include "utils/common/common.h"
+#include "utils/proc_pids/proc_pids.h"
+
+#define UTIL_NAME "utils_proc_pids"
+
+void pids_list_free(pids_list_t *list) {
+ assert(list);
+
+ sfree(list->pids);
+ sfree(list);
+}
+
+int proc_pids_is_name_valid(const char *name) {
+
+ if (name != NULL) {
+ unsigned len = strlen(name);
+ if (len > 0 && len <= MAX_PROC_NAME_LEN)
+ return 1;
+ else {
+ DEBUG(UTIL_NAME
+ ": Process name \'%s\' is too long. Max supported len is %d chars.",
+ name, MAX_PROC_NAME_LEN);
+ }
+ }
+
+ return 0;
+}
+
+int pids_list_add_pid(pids_list_t *list, const pid_t pid) {
+ assert(list);
+
+ if (list->allocated == list->size) {
+ size_t new_allocated = list->allocated + 1 + list->allocated / 10;
+ pid_t *new_pids = realloc(list->pids, sizeof(pid_t) * new_allocated);
+
+ if (NULL == new_pids) {
+ ERROR(UTIL_NAME ": Alloc error\n");
+ return -1;
+ }
+
+ list->pids = new_pids;
+ list->allocated = new_allocated;
+ }
+
+ list->pids[list->size] = pid;
+ list->size++;
+
+ return 0;
+}
+
+int pids_list_add_list(pids_list_t *dst, pids_list_t *src) {
+ assert(dst);
+ assert(src);
+
+ if (dst->allocated < dst->size + src->size) {
+ pid_t *new_pids =
+ realloc(dst->pids, sizeof(pid_t) * (dst->size + src->size));
+
+ if (NULL == new_pids) {
+ ERROR(UTIL_NAME ": Alloc error\n");
+ return -1;
+ }
+
+ dst->allocated = dst->size + src->size;
+ dst->pids = new_pids;
+ }
+
+ memcpy(dst->pids + dst->size, src->pids, src->size * sizeof(*(src->pids)));
+ dst->size += src->size;
+
+ return 0;
+}
+
+int pids_list_clear(pids_list_t *list) {
+ assert(list);
+
+ if (list->pids != NULL)
+ sfree(list->pids);
+
+ list->size = 0;
+ list->allocated = 0;
+
+ return 0;
+}
+
+int pids_list_contains_pid(pids_list_t *list, const pid_t pid) {
+ assert(list);
+
+ for (int i = 0; i < list->size; i++)
+ if (list->pids[i] == pid)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * NAME
+ * read_proc_name
+ *
+ * DESCRIPTION
+ * Reads process name from given pid directory.
+ * Strips new-line character (\n).
+ *
+ * PARAMETERS
+ * `procfs_path' Path to systems proc directory (e.g. /proc)
+ * `pid_entry' Dirent for PID directory
+ * `name' Output buffer for process name, recommended proc_comm.
+ * `out_size' Output buffer size, recommended sizeof(proc_comm)
+ *
+ * RETURN VALUE
+ * On success, the number of read bytes (includes stripped \n).
+ * -1 on file open error
+*/
+static int read_proc_name(const char *procfs_path,
+ const struct dirent *pid_entry, char *name,
+ const size_t out_size) {
+ assert(pid_entry);
+ assert(name);
+ assert(out_size);
+ memset(name, 0, out_size);
+
+ const char *comm_file_name = "comm";
+
+ char *path = ssnprintf_alloc("%s/%s/%s", procfs_path, pid_entry->d_name,
+ comm_file_name);
+ if (path == NULL)
+ return -1;
+ FILE *f = fopen(path, "r");
+ if (f == NULL) {
+ ERROR(UTIL_NAME ": Failed to open comm file, error: %d\n", errno);
+ sfree(path);
+ return -1;
+ }
+ size_t read_length = fread(name, sizeof(char), out_size, f);
+ name[out_size - 1] = '\0';
+ fclose(f);
+ sfree(path);
+ /* strip new line ending */
+ char *newline = strchr(name, '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+
+ return read_length;
+}
+
+/*
+ * NAME
+ * get_pid_number
+ *
+ * DESCRIPTION
+ * Gets pid number for given /proc/pid directory entry or
+ * returns error if input directory does not hold PID information.
+ *
+ * PARAMETERS
+ * `entry' Dirent for PID directory
+ * `pid' PID number to be filled
+ *
+ * RETURN VALUE
+ * 0 on success. -1 on error.
+ */
+static int get_pid_number(struct dirent *entry, pid_t *pid) {
+ char *tmp_end; /* used for strtoul error check*/
+
+ if (pid == NULL || entry == NULL)
+ return -1;
+
+ if (entry->d_type != DT_DIR)
+ return -1;
+
+ /* trying to get pid number from directory name*/
+ *pid = strtoul(entry->d_name, &tmp_end, 10);
+ if (*tmp_end != '\0') {
+ return -1; /* conversion failed, not proc-pid */
+ }
+ /* all checks passed, marking as success */
+ return 0;
+}
+
+int proc_pids_init(const char **procs_names_array,
+ const size_t procs_names_array_size,
+ proc_pids_t **proc_pids[]) {
+
+ proc_pids_t **proc_pids_array;
+ assert(proc_pids);
+ assert(NULL == *proc_pids);
+
+ /* Copy procs names to output array. Initialize pids list with NULL value. */
+ proc_pids_array = calloc(procs_names_array_size, sizeof(*proc_pids_array));
+
+ if (NULL == proc_pids_array)
+ return -1;
+
+ for (size_t i = 0; i < procs_names_array_size; ++i) {
+ proc_pids_array[i] = calloc(1, sizeof(**proc_pids_array));
+ if (NULL == proc_pids_array[i])
+ goto proc_pids_init_error;
+
+ sstrncpy(proc_pids_array[i]->process_name, procs_names_array[i],
+ STATIC_ARRAY_SIZE(proc_pids_array[i]->process_name));
+ proc_pids_array[i]->prev = NULL;
+ proc_pids_array[i]->curr = NULL;
+ }
+
+ *proc_pids = proc_pids_array;
+
+ return 0;
+proc_pids_init_error:
+ if (NULL != proc_pids_array) {
+ for (size_t i = 0; i < procs_names_array_size; ++i) {
+ free(proc_pids_array[i]);
+ }
+ free(proc_pids_array);
+ }
+ return -1;
+}
+
+static void swap_proc_pids(proc_pids_t **proc_pids, size_t proc_pids_num) {
+ for (size_t i = 0; i < proc_pids_num; i++) {
+ pids_list_t *swap = proc_pids[i]->prev;
+ proc_pids[i]->prev = proc_pids[i]->curr;
+ proc_pids[i]->curr = swap;
+ }
+}
+
+int proc_pids_update(const char *procfs_path, proc_pids_t **proc_pids,
+ size_t proc_pids_num) {
+ assert(procfs_path);
+ assert(proc_pids);
+
+ DIR *proc_dir = opendir(procfs_path);
+ if (proc_dir == NULL) {
+ ERROR(UTIL_NAME ": Could not open %s directory, error: %d", procfs_path,
+ errno);
+ return -1;
+ }
+
+ swap_proc_pids(proc_pids, proc_pids_num);
+
+ for (size_t i = 0; i < proc_pids_num; i++) {
+ if (NULL == proc_pids[i]->curr)
+ proc_pids[i]->curr = calloc(1, sizeof(*(proc_pids[i]->curr)));
+
+ if (NULL == proc_pids[i]->curr) {
+ ERROR(UTIL_NAME ": Alloc error\n");
+ goto update_error;
+ }
+
+ proc_pids[i]->curr->size = 0;
+ }
+
+ /* Go through procfs and find PIDS and their comms */
+ struct dirent *entry;
+ while ((entry = readdir(proc_dir)) != NULL) {
+ pid_t pid;
+ int pid_conversion = get_pid_number(entry, &pid);
+ if (pid_conversion < 0)
+ continue;
+
+ proc_comm_t comm;
+ int read_result =
+ read_proc_name(procfs_path, entry, comm, sizeof(proc_comm_t));
+ if (read_result <= 0)
+ continue;
+
+ /* Try to find comm in input procs array */
+ for (size_t i = 0; i < proc_pids_num; ++i) {
+ if (0 ==
+ strncmp(comm, proc_pids[i]->process_name, STATIC_ARRAY_SIZE(comm)))
+ pids_list_add_pid(proc_pids[i]->curr, pid);
+ }
+ }
+
+ int close_result = closedir(proc_dir);
+ if (0 != close_result) {
+ ERROR(UTIL_NAME ": failed to close /proc directory, error: %d", errno);
+ goto update_error;
+ }
+ return 0;
+
+update_error:
+ swap_proc_pids(proc_pids, proc_pids_num);
+ return -1;
+}
+
+int pids_list_diff(proc_pids_t *proc, pids_list_t *added,
+ pids_list_t *removed) {
+ assert(proc);
+ assert(added);
+ assert(removed);
+
+ added->size = 0;
+ removed->size = 0;
+
+ if (NULL == proc->prev || 0 == proc->prev->size) {
+ /* append all PIDs from curr to added*/
+ return pids_list_add_list(added, proc->curr);
+ } else if (NULL == proc->curr || 0 == proc->curr->size) {
+ /* append all PIDs from prev to removed*/
+ return pids_list_add_list(removed, proc->prev);
+ }
+
+ for (int i = 0; i < proc->prev->size; i++)
+ if (0 == pids_list_contains_pid(proc->curr, proc->prev->pids[i])) {
+ int add_result = pids_list_add_pid(removed, proc->prev->pids[i]);
+ if (add_result < 0)
+ return add_result;
+ }
+
+ for (int i = 0; i < proc->curr->size; i++)
+ if (0 == pids_list_contains_pid(proc->prev, proc->curr->pids[i])) {
+ int add_result = pids_list_add_pid(added, proc->curr->pids[i]);
+ if (add_result < 0)
+ return add_result;
+ }
+
+ return 0;
+}
+
+int proc_pids_free(proc_pids_t *proc_pids[], size_t proc_pids_num) {
+ for (size_t i = 0; i < proc_pids_num; i++) {
+ if (NULL != proc_pids[i]->curr)
+ pids_list_free(proc_pids[i]->curr);
+ if (NULL != proc_pids[i]->prev)
+ pids_list_free(proc_pids[i]->prev);
+ sfree(proc_pids[i]);
+ }
+ sfree(proc_pids);
+
+ return 0;
+}
--- /dev/null
+/**
+ * collectd - src/utils/proc_pids/proc_pids.h
+ *
+ * Copyright(c) 2018-2019 Intel Corporation. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ * Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ * Michał Aleksiński <michalx.aleksinski@intel.com>
+ **/
+
+#include <dirent.h>
+#include <sys/types.h>
+
+/*
+ * Process name inside comm file is limited to 16 chars.
+ * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
+ */
+#define MAX_PROC_NAME_LEN 16
+
+/* Helper typedef for process name array
+ * Extra 1 char is added for string null termination.
+ */
+typedef char proc_comm_t[MAX_PROC_NAME_LEN + 1];
+
+/* List of pids. */
+typedef struct pids_list_s {
+ pid_t *pids;
+ size_t size;
+ size_t allocated;
+} pids_list_t;
+
+/* Holds process name and list of pids assigned to that name */
+typedef struct proc_pids_s {
+ proc_comm_t process_name;
+ pids_list_t *prev;
+ pids_list_t *curr;
+} proc_pids_t;
+
+/*
+ * NAME
+ * pids_list_free
+ *
+ * DESCRIPTION
+ * Free all elements of given pids list
+ *
+ * PARAMETERS
+ * `list' Head of target pids_list.
+ */
+void pids_list_free(pids_list_t *list);
+
+/*
+ * NAME
+ * pids_list_add_pid
+ *
+ * DESCRIPTION
+ * Adds pid at the end of the pids array.
+ * Reallocates memory for new pid element, it is up to user to free it.
+ *
+ * PARAMETERS
+ * `list' Target pids_list.
+ * `pid' Pid to be added.
+ *
+ * RETURN VALUE
+ * On success, returns 0.
+ * -1 on memory allocation error.
+ */
+int pids_list_add_pid(pids_list_t *list, const pid_t pid);
+
+/*
+ * NAME
+ * pids_list_clear
+ *
+ * DESCRIPTION
+ * Remove all pids from the list
+ *
+ * PARAMETERS
+ * `list' Target pids_list.
+ *
+ * RETURN VALUE
+ * On success, return 0
+ */
+int pids_list_clear(pids_list_t *list);
+
+/*
+ * NAME
+ * pids_list_add_list
+ *
+ * DESCRIPTION
+ * Adds pids list at the end of the pids list.
+ * Allocates memory for new pid elements, it is up to user to free it.
+ *
+ * PARAMETERS
+ * `dst' Target PIDs list.
+ * `src' Source PIDs list.
+ *
+ * RETURN VALUE
+ * On success, returns 0.
+ * -1 on memory allocation error.
+ */
+int pids_list_add_list(pids_list_t *dst, pids_list_t *src);
+
+/*
+ * NAME
+ * pids_list_contains_pid
+ *
+ * DESCRIPTION
+ * Tests if pids list contains specific pid.
+ *
+ * PARAMETERS
+ * `list' pids_list to check.
+ * `pid' Pid to be searched for.
+ *
+ * RETURN VALUE
+ * If PID found in list, returns 1,
+ * Otherwise returns 0.
+ */
+int pids_list_contains_pid(pids_list_t *list, const pid_t pid);
+
+/*
+ * NAME
+ * pids_list_diff
+ *
+ * DESCRIPTION
+ * Searches for differences in two given lists
+ *
+ * PARAMETERS
+ * `proc' List of pids
+ * `added' New pids which appeared
+ * `removed' Result array storing pids which disappeared
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+int pids_list_diff(proc_pids_t *proc, pids_list_t *added, pids_list_t *removed);
+
+/*
+ * NAME
+ * proc_pids_is_name_valid
+ *
+ * DESCRIPTION
+ * Checks if given string is valid process name.
+ *
+ * PARAMETERS
+ * `name' null-terminated char array
+ *
+ * RETURN VALUE
+ * If given name is a valid process name, returns 1,
+ * Otherwise returns 0.
+ */
+int proc_pids_is_name_valid(const char *name);
+
+/*
+ * NAME
+ * proc_pids_init
+ *
+ * DESCRIPTION
+ * Helper function to properly initialize array of proc_pids.
+ * Allocates memory for proc_pids structs.
+ *
+ * PARAMETERS
+ * `procs_names_array' Array of null-terminated strings with
+ * process' names to be copied to new array
+ * `procs_names_array_size' procs_names_array element count
+ * `proc_pids' Address of pointer, under which new
+ * array of proc_pids will be allocated.
+ * Must be NULL.
+ * RETURN VALUE
+ * 0 on success. Negative number on error:
+ * -1: allocation error
+ */
+int proc_pids_init(const char **procs_names_array,
+ const size_t procs_names_array_size,
+ proc_pids_t **proc_pids[]);
+
+/*
+ * NAME
+ * proc_pids_update
+ *
+ * DESCRIPTION
+ * Updates PIDs matching processes's names.
+ * Searches all PID directories in /proc fs and updates current pids_list.
+ *
+ * PARAMETERS
+ * `procfs_path' Path to systems proc directory (e.g. /proc)
+ * `proc_pids' Array of proc_pids pointers to be updated.
+ * `proc_pids_num' proc_pids element count
+ *
+ * RETURN VALUE
+ * 0 on success. -1 on error.
+ */
+int proc_pids_update(const char *procfs_path, proc_pids_t *proc_pids[],
+ size_t proc_pids_num);
+
+/*
+ * NAME
+ * proc_pids_free
+ *
+ * DESCRIPTION
+ * Releses memory allocatd for proc_pids
+ *
+ * PARAMETERS
+ * `proc_pids' Array of proc_pids
+ * `proc_pids_num' proc_pids element count
+ *
+ * RETURN VALUE
+ * 0 on success. -1 on error.
+ */
+int proc_pids_free(proc_pids_t *proc_pids[], size_t proc_pids_num);
--- /dev/null
+#include "testing.h"
+#include "utils/proc_pids/proc_pids.c" /* sic */
+#include <sys/stat.h>
+
+/***************************************************************************
+ * helper functions
+ */
+
+typedef struct stub_proc_pid {
+ proc_comm_t comm;
+ pid_t pid;
+} stub_proc_pid_t;
+
+static const char *proc_fs = "/tmp/procfs_stub";
+
+/*
+ * NAME
+ * stub_procfs_setup
+ *
+ * DESCRIPTION
+ * Prepares testing environment by creating temporary
+ * PID/comm file structure.
+ *
+ * PARAMETERS
+ * `proc_pids_array' Array of stub_proc_pid_t structs. Represents
+ * which PIDs should hold given process name.
+ * `proc_pids_array_length' Element count of input array.
+ *
+ * RETURN VALUE
+ * 0 on success.
+ * -1 on base dir creation error.
+ * -2 on comm file creation error.
+ * -3 on comm file write error.
+ */
+int stub_procfs_setup(const stub_proc_pid_t *proc_pids_array,
+ const size_t proc_pids_array_length) {
+ if (mkdir(proc_fs, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0)
+ return -1;
+ char path[256];
+
+ for (size_t i = 0; i < proc_pids_array_length; ++i) {
+ memset(path, 0, sizeof(path));
+ snprintf(path, STATIC_ARRAY_SIZE(path), "%s/%d", proc_fs,
+ proc_pids_array[i].pid);
+ mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ strncat(path, "/comm", STATIC_ARRAY_SIZE(path) - strlen(path) - 1);
+
+ FILE *fp = fopen(path, "w");
+ if (!fp)
+ return -2;
+
+ size_t slen = strlen(proc_pids_array[i].comm);
+ size_t wlen = fwrite(proc_pids_array[i].comm, sizeof(char), slen, fp);
+ fclose(fp);
+
+ if (slen != wlen)
+ return -3;
+ }
+ return 0;
+}
+
+/*
+ * NAME
+ * stub_procfs_teardown
+ *
+ * DESCRIPTION
+ * Clears testing environment: removes stub proc files.
+ * NOTE - This function could be implemented by usage of nftw, but this
+ * would require #define _XOPEN_SOURCE 500, which
+ * messes up intel_rdt includes.
+ *
+ * RETURN VALUE
+ * system command result
+ */
+int stub_procfs_teardown() {
+ char cmd[256];
+ sstrncpy(cmd, "rm -rf ", STATIC_ARRAY_SIZE(cmd));
+ strncat(cmd, proc_fs, STATIC_ARRAY_SIZE(cmd) - strlen(cmd) - 1);
+ return system(cmd);
+}
+
+/* Max PID value. More info:
+ * http://web.archive.org/web/20111209081734/http://research.cs.wisc.edu/condor/condorg/linux_scalability.html
+ */
+#define MAX_PID 4194304
+#define MAX_PID_STR "4194304"
+
+/***************************************************************************
+ * tests
+ */
+DEF_TEST(proc_pids_init__on_nullptr) {
+ /* setup */
+ const char *procs_names_array[] = {"proc1", "proc2", "proc3"};
+ const size_t procs_names_array_size = STATIC_ARRAY_SIZE(procs_names_array);
+ proc_pids_t **proc_pids_array = NULL;
+
+ /* check */
+ int result = proc_pids_init(procs_names_array, procs_names_array_size,
+ &proc_pids_array);
+ EXPECT_EQ_INT(0, result);
+ for (size_t i = 0; i < procs_names_array_size; ++i)
+ EXPECT_EQ_STR(procs_names_array[i], proc_pids_array[i]->process_name);
+
+ /* cleanup */
+ proc_pids_free(proc_pids_array, procs_names_array_size);
+ return 0;
+}
+
+DEF_TEST(pid_list_add_pid__empty_list) {
+ /* setup */
+ pids_list_t *proc_pids_instance = calloc(1, sizeof(*proc_pids_instance));
+ pid_t pid = 1234;
+
+ /* check */
+ pids_list_add_pid(proc_pids_instance, pid);
+ EXPECT_EQ_INT(pid, proc_pids_instance->pids[0]);
+
+ /* cleanup */
+ pids_list_free(proc_pids_instance);
+ return 0;
+}
+
+DEF_TEST(pid_list_add_pid__non_empty_list) {
+ /* setup */
+ pids_list_t *proc_pids_instance = calloc(1, sizeof(*proc_pids_instance));
+ pid_t pids[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+
+ /* check */
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids); ++i)
+ pids_list_add_pid(proc_pids_instance, pids[i]);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids); ++i) {
+ EXPECT_EQ_INT(pids[i], proc_pids_instance->pids[i]);
+ }
+
+ /* cleanup */
+ pids_list_free(proc_pids_instance);
+ return 0;
+}
+
+DEF_TEST(pids_list_add_pids_list__non_empty_lists) {
+ /* setup */
+ pid_t pids_array_1[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ pid_t pids_array_2[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+ pids_list_t *pids_list_1 = calloc(1, sizeof(*pids_list_1));
+ pids_list_t *pids_list_2 = calloc(1, sizeof(*pids_list_2));
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_1); ++i) {
+ pids_list_add_pid(pids_list_1, pids_array_1[i]);
+ pids_list_add_pid(pids_list_2, pids_array_2[i]);
+ }
+
+ /* check */
+ int result = pids_list_add_list(pids_list_1, pids_list_2);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_2) +
+ STATIC_ARRAY_SIZE(pids_array_1),
+ pids_list_1->size);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_1); ++i) {
+ EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array_1[i]));
+ EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array_2[i]));
+ }
+
+ /* setup */
+ pids_list_free(pids_list_1);
+ pids_list_free(pids_list_2);
+ return 0;
+}
+
+DEF_TEST(pids_list_add_pids_list__add_to_empty) {
+ /* setup */
+ pid_t pids_array[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+ pids_list_t *pids_list_1 = calloc(1, sizeof(*pids_list_1));
+ pids_list_t *pids_list_2 = calloc(1, sizeof(*pids_list_2));
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array); ++i)
+ pids_list_add_pid(pids_list_2, pids_array[i]);
+
+ /* check */
+ int result = pids_list_add_list(pids_list_1, pids_list_2);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array), pids_list_1->size);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array); ++i)
+ EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array[i]));
+
+ /* setup */
+ pids_list_free(pids_list_1);
+ pids_list_free(pids_list_2);
+ return 0;
+}
+
+DEF_TEST(get_pid_number__valid_dir) {
+ /* setup */
+ struct dirent d;
+ sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+ pid_t pid = 0;
+
+ /* check */
+ int pid_conversion = get_pid_number(&d, &pid);
+
+ EXPECT_EQ_INT(0, pid_conversion);
+ EXPECT_EQ_INT(MAX_PID, pid);
+
+ /* cleanup */
+ return 0;
+}
+
+DEF_TEST(get_pid_number__invalid_dir_name) {
+ /* setup */
+ struct dirent d;
+ sstrncpy(d.d_name, "invalid", STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+ pid_t pid = 0;
+
+ /* check */
+ int pid_conversion = get_pid_number(&d, &pid);
+
+ EXPECT_EQ_INT(-1, pid_conversion);
+ EXPECT_EQ_INT(0, pid);
+
+ /* cleanup */
+ return 0;
+}
+
+DEF_TEST(read_proc_name__valid_name) {
+ /* setup */
+ stub_proc_pid_t pp_stubs[] = {{"proc1", MAX_PID}};
+ stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+ struct dirent d;
+ sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+
+ /* check */
+ proc_comm_t comm;
+ int read_result = read_proc_name(proc_fs, &d, comm, STATIC_ARRAY_SIZE(comm));
+
+ EXPECT_EQ_INT(strlen(pp_stubs[0].comm), read_result);
+ EXPECT_EQ_STR(pp_stubs[0].comm, comm);
+
+ /* cleanup */
+ stub_procfs_teardown();
+ return 0;
+}
+
+DEF_TEST(read_proc_name__invalid_name) {
+ /* setup */
+ struct dirent d;
+ sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+
+ /* check */
+ proc_comm_t comm;
+ int read_result = read_proc_name(proc_fs, &d, comm, STATIC_ARRAY_SIZE(comm));
+
+ EXPECT_EQ_INT(-1, read_result);
+
+ /* cleanup */
+ return 0;
+}
+
+DEF_TEST(proc_pids_update__one_proc_many_pid) {
+ /* setup */
+ const char *proc_names[] = {"proc1"};
+ stub_proc_pid_t pp_stubs[] = {{"proc1", 1007},
+ {"proc1", 1008},
+ {"proc1", 1009},
+ {"proc2", 1010},
+ {"proc3", 1011}};
+ proc_pids_t **proc_pids = NULL;
+ int result;
+ stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+
+ result =
+ proc_pids_init(proc_names, STATIC_ARRAY_SIZE(proc_names), &proc_pids);
+ EXPECT_EQ_INT(0, result);
+
+ /* check */
+ result = proc_pids_update(proc_fs, proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ EXPECT_EQ_INT(0, result);
+
+ /* proc name check */
+ EXPECT_EQ_STR(proc_names[0], proc_pids[0]->process_name);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pp_stubs); ++i) {
+ if (0 == strcmp(pp_stubs[i].comm, proc_names[0]))
+ /* check if proc struct has correct pids */
+ EXPECT_EQ_INT(pids_list_contains_pid(proc_pids[0]->curr, pp_stubs[i].pid),
+ 1);
+ else
+ /* check if proc struct has no incorrect pids */
+ EXPECT_EQ_INT(pids_list_contains_pid(proc_pids[0]->curr, pp_stubs[i].pid),
+ 0);
+ }
+
+ /* cleanup */
+ proc_pids_free(proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ stub_procfs_teardown();
+ return 0;
+}
+
+DEF_TEST(proc_pids_update__many_proc_many_pid) {
+ /* setup */
+ const char *proc_names[] = {"proc1", "proc2", "proc3"};
+ stub_proc_pid_t pp_stubs[] = {
+ {"proc1", 1007}, {"proc1", 1008}, {"proc1", 1009}, {"proc2", 2007},
+ {"proc2", 2008}, {"proc2", 2009}, {"proc3", 3007}, {"proc3", 3008},
+ {"proc3", 3009}, {"proc4", 4007}, {"proc4", 4008}, {"proc4", 4009},
+ {"proc5", 5007}, {"proc5", 5008}, {"proc5", 5009}};
+ proc_pids_t **proc_pids = NULL;
+ int result;
+ stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+
+ result =
+ proc_pids_init(proc_names, STATIC_ARRAY_SIZE(proc_names), &proc_pids);
+ EXPECT_EQ_INT(0, result);
+
+ /* check */
+ result = proc_pids_update(proc_fs, proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ EXPECT_EQ_INT(0, result);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(proc_names); ++i) {
+
+ /* proc name check */
+ EXPECT_EQ_STR(proc_names[i], proc_pids[i]->process_name);
+
+ for (size_t j = 0; j < STATIC_ARRAY_SIZE(pp_stubs); ++j) {
+ if (0 == strcmp(pp_stubs[j].comm, proc_names[i]))
+ /* check if proc struct has correct pids */
+ EXPECT_EQ_INT(
+ pids_list_contains_pid(proc_pids[i]->curr, pp_stubs[j].pid), 1);
+ else
+ /* check if proc struct has no incorrect pids */
+ EXPECT_EQ_INT(
+ pids_list_contains_pid(proc_pids[i]->curr, pp_stubs[j].pid), 0);
+ }
+ }
+
+ /* cleanup */
+ proc_pids_free(proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ stub_procfs_teardown();
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__all_changed) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ pid_t pids_array_after[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_after;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_before), lost_pids->size);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_after), new_pids->size);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_before); ++i) {
+ EXPECT_EQ_INT(1, pids_list_contains_pid(new_pids, pids_array_after[i]));
+ EXPECT_EQ_INT(1, pids_list_contains_pid(lost_pids, pids_array_before[i]));
+ }
+
+ /* cleanup */
+ pids_list_free(new_pids);
+ pids_list_free(lost_pids);
+
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__nothing_changed) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_before;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_before);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(0, lost_pids->size);
+ EXPECT_EQ_INT(0, new_pids->size);
+
+ /* cleanup */
+ pids_list_free(lost_pids);
+ pids_list_free(new_pids);
+
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__one_added) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ pid_t pids_array_after[] = {1000, 1001, 1002, 1003, 1004,
+ 1005, 1006, 1007, 1008};
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_after;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(0, lost_pids->size);
+ EXPECT_EQ_INT(1, new_pids->size);
+ EXPECT_EQ_INT(1008, new_pids->pids[0]);
+
+ /* cleanup */
+ pids_list_free(lost_pids);
+ pids_list_free(new_pids);
+
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__one_removed) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004,
+ 1005, 1006, 1007, 1008};
+ pid_t pids_array_after[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_after;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(0, new_pids->size);
+ EXPECT_EQ_INT(1, lost_pids->size);
+ EXPECT_EQ_INT(1008, lost_pids->pids[0]);
+
+ /* cleanup */
+ pids_list_free(lost_pids);
+ pids_list_free(new_pids);
+
+ return 0;
+}
+
+int main(void) {
+ stub_procfs_teardown();
+ RUN_TEST(proc_pids_init__on_nullptr);
+ RUN_TEST(pid_list_add_pid__empty_list);
+ RUN_TEST(pid_list_add_pid__non_empty_list);
+ RUN_TEST(pids_list_add_pids_list__non_empty_lists);
+ RUN_TEST(pids_list_add_pids_list__add_to_empty);
+ RUN_TEST(get_pid_number__valid_dir);
+ RUN_TEST(get_pid_number__invalid_dir_name);
+ RUN_TEST(read_proc_name__valid_name);
+ RUN_TEST(read_proc_name__invalid_name);
+ RUN_TEST(proc_pids_update__one_proc_many_pid);
+ RUN_TEST(proc_pids_update__many_proc_many_pid);
+ RUN_TEST(pids_list_diff__all_changed);
+ RUN_TEST(pids_list_diff__nothing_changed);
+ RUN_TEST(pids_list_diff__one_added);
+ RUN_TEST(pids_list_diff__one_removed);
+ stub_procfs_teardown();
+ END_TEST;
+}
#endif
/*
- virConnectListAllDomains() appeared in 0.10.2
- Note that LIBVIR_CHECK_VERSION appeared a year later, so
- in some systems which actually have virConnectListAllDomains()
+ virConnectListAllDomains() appeared in 0.10.2 (Sep 2012)
+ Note that LIBVIR_CHECK_VERSION appeared a year later (Dec 2013,
+ libvirt-1.2.0),
+ so in some systems which actually have virConnectListAllDomains()
we can't detect this.
*/
#if LIBVIR_CHECK_VERSION(0, 10, 2)
#define HAVE_DOM_REASON_POSTCOPY 1
#endif
+#if LIBVIR_CHECK_VERSION(4, 10, 0)
+#define HAVE_DOM_REASON_SHUTOFF_DAEMON 1
+#endif
#endif /* LIBVIR_CHECK_VERSION */
/* structure used for aggregating notification-thread data*/
bool is_active;
} virt_notif_thread_t;
-static const char *config_keys[] = {"Connection",
-
- "RefreshInterval",
-
- "Domain",
- "BlockDevice",
- "BlockDeviceFormat",
- "BlockDeviceFormatBasename",
- "InterfaceDevice",
- "IgnoreSelected",
-
- "HostnameFormat",
- "HostnameMetadataNS",
- "HostnameMetadataXPath",
- "InterfaceFormat",
-
- "PluginInstanceFormat",
-
- "Instances",
- "ExtraStats",
- "PersistentNotification",
-
- "ReportBlockDevices",
- "ReportNetworkInterfaces",
- NULL};
-
/* PersistentNotification is false by default */
static bool persistent_notification = false;
switch (detail) {
case VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED: /* Guest finished shutdown
sequence */
+#ifdef LIBVIR_CHECK_VERSION
+#if LIBVIR_CHECK_VERSION(3, 4, 0)
+ case VIR_DOMAIN_EVENT_SHUTDOWN_GUEST: /* Domain finished shutting down after
+ request from the guest itself (e.g.
+ hardware-specific action) */
+ case VIR_DOMAIN_EVENT_SHUTDOWN_HOST: /* Domain finished shutting down after
+ request from the host (e.g. killed
+ by a signal) */
+#endif
+#endif
ret = VIR_DOMAIN_SHUTDOWN_USER;
break;
default:
"domain failed to start",
[VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT] =
"restored from a snapshot which was taken while domain was shutoff",
+#ifdef HAVE_DOM_REASON_SHUTOFF_DAEMON
+ [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_DAEMON] =
+ "daemon decides to kill domain during reconnection processing",
+#endif
[VIR_DOMAIN_CRASHED][VIR_DOMAIN_CRASHED_UNKNOWN] =
"the reason is unknown",
};
#endif /* HAVE_DOM_REASON */
-#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
#define NANOSEC_IN_SEC 1e9
#define GET_STATS(_f, _name, ...) \
static time_t last_refresh = (time_t)0;
static int refresh_lists(struct lv_read_instance *inst);
+static int register_event_impl(void);
+static int start_event_loop(virt_notif_thread_t *thread_data);
struct lv_block_stats {
virDomainBlockStatsStruct bi;
static void memory_stats_submit(gauge_t value, virDomainPtr dom,
int tag_index) {
- static const char *tags[] = {"swap_in", "swap_out", "major_fault",
- "minor_fault", "unused", "available",
- "actual_balloon", "rss", "usable",
- "last_update"};
+ static const char *tags[] = {"swap_in", "swap_out", "major_fault",
+ "minor_fault", "unused", "available",
+ "actual_balloon", "rss", "usable",
+ "last_update", "disk_caches"};
if ((tag_index < 0) || (tag_index >= (int)STATIC_ARRAY_SIZE(tags))) {
ERROR("virt plugin: Array index out of bounds: tag_index = %d", tag_index);
sfree(dev_copy);
}
-static unsigned int parse_ex_stats_flags(char **exstats, int numexstats) {
+/**
+ * Function for parsing ExtraStats configuration options.
+ * Result of parsing is stored under 'out_parsed_flags' pointer.
+ *
+ * Returns 0 in case of success and 1 in case of parsing error
+ */
+static int parse_ex_stats_flags(unsigned int *out_parsed_flags, char **exstats,
+ int numexstats) {
unsigned int ex_stats_flags = ex_stats_none;
+
+ assert(out_parsed_flags != NULL);
+
for (int i = 0; i < numexstats; i++) {
for (int j = 0; ex_stats_table[j].name != NULL; j++) {
if (strcasecmp(exstats[i], ex_stats_table[j].name) == 0) {
if (ex_stats_table[j + 1].name == NULL) {
ERROR(PLUGIN_NAME " plugin: Unmatched ExtraStats option: %s",
exstats[i]);
+ return 1;
}
}
}
- return ex_stats_flags;
+
+ *out_parsed_flags = ex_stats_flags;
+ return 0;
}
static void domain_state_submit_notif(virDomainPtr dom, int state, int reason) {
return 0;
}
-static int lv_config(const char *key, const char *value) {
- if (virInitialize() != 0)
- return 1;
-
- if (lv_init_ignorelists() != 0)
- return 1;
-
- if (strcasecmp(key, "Connection") == 0) {
- char *tmp = strdup(value);
- if (tmp == NULL) {
- ERROR(PLUGIN_NAME " plugin: Connection strdup failed.");
- return 1;
- }
- sfree(conn_string);
- conn_string = tmp;
- return 0;
- }
-
- if (strcasecmp(key, "RefreshInterval") == 0) {
- char *eptr = NULL;
- interval = strtol(value, &eptr, 10);
- if (eptr == NULL || *eptr != '\0')
- return 1;
- return 0;
+/* Validates config option that may take multiple strings arguments.
+ * Returns 0 on success, -1 otherwise */
+static int check_config_multiple_string_entry(const oconfig_item_t *ci) {
+ if (ci == NULL) {
+ ERROR(PLUGIN_NAME " plugin: ci oconfig_item can't be NULL");
+ return -1;
}
- if (strcasecmp(key, "Domain") == 0) {
- if (ignorelist_add(il_domains, value))
- return 1;
- return 0;
- }
- if (strcasecmp(key, "BlockDevice") == 0) {
- if (ignorelist_add(il_block_devices, value))
- return 1;
- return 0;
+ if (ci->values_num < 1) {
+ ERROR(PLUGIN_NAME
+ " plugin: the '%s' option requires at least one string argument",
+ ci->key);
+ return -1;
}
- if (strcasecmp(key, "BlockDeviceFormat") == 0) {
- if (strcasecmp(value, "target") == 0)
- blockdevice_format = target;
- else if (strcasecmp(value, "source") == 0)
- blockdevice_format = source;
- else {
- ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s", value);
+ for (int i = 0; i < ci->values_num; ++i) {
+ if (ci->values[i].type != OCONFIG_TYPE_STRING) {
+ ERROR(PLUGIN_NAME
+ " plugin: one of the '%s' options is not a valid string",
+ ci->key);
return -1;
}
- return 0;
- }
- if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
- blockdevice_format_basename = IS_TRUE(value) ? true : false;
- return 0;
- }
- if (strcasecmp(key, "InterfaceDevice") == 0) {
- if (ignorelist_add(il_interface_devices, value))
- return 1;
- return 0;
}
- if (strcasecmp(key, "IgnoreSelected") == 0) {
- if (IS_TRUE(value)) {
- ignorelist_set_invert(il_domains, 0);
- ignorelist_set_invert(il_block_devices, 0);
- ignorelist_set_invert(il_interface_devices, 0);
- } else {
- ignorelist_set_invert(il_domains, 1);
- ignorelist_set_invert(il_block_devices, 1);
- ignorelist_set_invert(il_interface_devices, 1);
- }
- return 0;
- }
+ return 0;
+}
- if (strcasecmp(key, "HostnameMetadataNS") == 0) {
- char *tmp = strdup(value);
- if (tmp == NULL) {
- ERROR(PLUGIN_NAME " plugin: HostnameMetadataNS strdup failed.");
- return 1;
- }
- sfree(hm_ns);
- hm_ns = tmp;
- return 0;
+static int lv_config(oconfig_item_t *ci) {
+ if (lv_init_ignorelists() != 0) {
+ ERROR(PLUGIN_NAME " plugin: lv_init_ignorelist failed.");
+ return -1;
}
- if (strcasecmp(key, "HostnameMetadataXPath") == 0) {
- char *tmp = strdup(value);
- if (tmp == NULL) {
- ERROR(PLUGIN_NAME " plugin: HostnameMetadataXPath strdup failed.");
- return 1;
- }
- sfree(hm_xpath);
- hm_xpath = tmp;
- return 0;
- }
+ for (int i = 0; i < ci->children_num; ++i) {
+ oconfig_item_t *c = ci->children + i;
- if (strcasecmp(key, "HostnameFormat") == 0) {
- char *value_copy = strdup(value);
- if (value_copy == NULL) {
- ERROR(PLUGIN_NAME " plugin: strdup failed.");
- return -1;
- }
+ if (strcasecmp(c->key, "Connection") == 0) {
+ if (cf_util_get_string(c, &conn_string) != 0 || conn_string == NULL)
+ return -1;
- char *fields[HF_MAX_FIELDS];
- int n = strsplit(value_copy, fields, HF_MAX_FIELDS);
- if (n < 1) {
- sfree(value_copy);
- ERROR(PLUGIN_NAME " plugin: HostnameFormat: no fields");
- return -1;
- }
+ continue;
+ } else if (strcasecmp(c->key, "RefreshInterval") == 0) {
+ if (cf_util_get_int(c, &interval) != 0)
+ return -1;
- for (int i = 0; i < n; ++i) {
- if (strcasecmp(fields[i], "hostname") == 0)
- hostname_format[i] = hf_hostname;
- else if (strcasecmp(fields[i], "name") == 0)
- hostname_format[i] = hf_name;
- else if (strcasecmp(fields[i], "uuid") == 0)
- hostname_format[i] = hf_uuid;
- else if (strcasecmp(fields[i], "metadata") == 0)
- hostname_format[i] = hf_metadata;
+ continue;
+ } else if (strcasecmp(c->key, "Domain") == 0) {
+ char *domain_name = NULL;
+ if (cf_util_get_string(c, &domain_name) != 0)
+ return -1;
+
+ if (ignorelist_add(il_domains, domain_name)) {
+ ERROR(PLUGIN_NAME " plugin: Adding '%s' to domain-ignorelist failed",
+ domain_name);
+ sfree(domain_name);
+ return -1;
+ }
+
+ sfree(domain_name);
+ continue;
+ } else if (strcasecmp(c->key, "BlockDevice") == 0) {
+ char *device_name = NULL;
+ if (cf_util_get_string(c, &device_name) != 0)
+ return -1;
+
+ if (ignorelist_add(il_block_devices, device_name) != 0) {
+ ERROR(PLUGIN_NAME
+ " plugin: Adding '%s' to block-device-ignorelist failed",
+ device_name);
+ sfree(device_name);
+ return -1;
+ }
+
+ sfree(device_name);
+ continue;
+ } else if (strcasecmp(c->key, "BlockDeviceFormat") == 0) {
+ char *device_format = NULL;
+ if (cf_util_get_string(c, &device_format) != 0)
+ return -1;
+
+ if (strcasecmp(device_format, "target") == 0)
+ blockdevice_format = target;
+ else if (strcasecmp(device_format, "source") == 0)
+ blockdevice_format = source;
else {
- ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
- fields[i]);
- sfree(value_copy);
+ ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s",
+ device_format);
+ sfree(device_format);
return -1;
}
- }
- sfree(value_copy);
- for (int i = n; i < HF_MAX_FIELDS; ++i)
- hostname_format[i] = hf_none;
+ sfree(device_format);
+ continue;
+ } else if (strcasecmp(c->key, "BlockDeviceFormatBasename") == 0) {
+ if (cf_util_get_boolean(c, &blockdevice_format_basename) != 0)
+ return -1;
- return 0;
- }
+ continue;
+ } else if (strcasecmp(c->key, "InterfaceDevice") == 0) {
+ char *interface_name = NULL;
+ if (cf_util_get_string(c, &interface_name) != 0)
+ return -1;
- if (strcasecmp(key, "PluginInstanceFormat") == 0) {
- char *value_copy = strdup(value);
- if (value_copy == NULL) {
- ERROR(PLUGIN_NAME " plugin: strdup failed.");
- return -1;
- }
+ if (ignorelist_add(il_interface_devices, interface_name)) {
+ ERROR(PLUGIN_NAME " plugin: Adding '%s' to interface-ignorelist failed",
+ interface_name);
+ sfree(interface_name);
+ return -1;
+ }
- char *fields[PLGINST_MAX_FIELDS];
- int n = strsplit(value_copy, fields, PLGINST_MAX_FIELDS);
- if (n < 1) {
- sfree(value_copy);
- ERROR(PLUGIN_NAME " plugin: PluginInstanceFormat: no fields");
- return -1;
- }
+ sfree(interface_name);
+ continue;
+ } else if (strcasecmp(c->key, "IgnoreSelected") == 0) {
+ bool ignore_selected = false;
+ if (cf_util_get_boolean(c, &ignore_selected) != 0)
+ return -1;
- for (int i = 0; i < n; ++i) {
- if (strcasecmp(fields[i], "none") == 0) {
- plugin_instance_format[i] = plginst_none;
- break;
- } else if (strcasecmp(fields[i], "name") == 0)
- plugin_instance_format[i] = plginst_name;
- else if (strcasecmp(fields[i], "uuid") == 0)
- plugin_instance_format[i] = plginst_uuid;
- else if (strcasecmp(fields[i], "metadata") == 0)
- plugin_instance_format[i] = plginst_metadata;
- else {
- ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
- fields[i]);
- sfree(value_copy);
+ if (ignore_selected) {
+ ignorelist_set_invert(il_domains, 0);
+ ignorelist_set_invert(il_block_devices, 0);
+ ignorelist_set_invert(il_interface_devices, 0);
+ } else {
+ ignorelist_set_invert(il_domains, 1);
+ ignorelist_set_invert(il_block_devices, 1);
+ ignorelist_set_invert(il_interface_devices, 1);
+ }
+
+ continue;
+ } else if (strcasecmp(c->key, "HostnameMetadataNS") == 0) {
+ if (cf_util_get_string(c, &hm_ns) != 0)
+ return -1;
+
+ continue;
+ } else if (strcasecmp(c->key, "HostnameMetadataXPath") == 0) {
+ if (cf_util_get_string(c, &hm_xpath) != 0)
+ return -1;
+
+ continue;
+ } else if (strcasecmp(c->key, "HostnameFormat") == 0) {
+ /* this option can take multiple strings arguments in one config line*/
+ if (check_config_multiple_string_entry(c) != 0) {
+ ERROR(PLUGIN_NAME " plugin: Could not get 'HostnameFormat' parameter");
return -1;
}
- }
- sfree(value_copy);
- for (int i = n; i < PLGINST_MAX_FIELDS; ++i)
- plugin_instance_format[i] = plginst_none;
+ const int params_num = c->values_num;
+ for (int i = 0; i < params_num; ++i) {
+ const char *param_name = c->values[i].value.string;
+ if (strcasecmp(param_name, "hostname") == 0)
+ hostname_format[i] = hf_hostname;
+ else if (strcasecmp(param_name, "name") == 0)
+ hostname_format[i] = hf_name;
+ else if (strcasecmp(param_name, "uuid") == 0)
+ hostname_format[i] = hf_uuid;
+ else if (strcasecmp(param_name, "metadata") == 0)
+ hostname_format[i] = hf_metadata;
+ else {
+ ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
+ param_name);
+ return -1;
+ }
+ }
- return 0;
- }
+ for (int i = params_num; i < HF_MAX_FIELDS; ++i)
+ hostname_format[i] = hf_none;
- if (strcasecmp(key, "InterfaceFormat") == 0) {
- if (strcasecmp(value, "name") == 0)
- interface_format = if_name;
- else if (strcasecmp(value, "address") == 0)
- interface_format = if_address;
- else if (strcasecmp(value, "number") == 0)
- interface_format = if_number;
- else {
- ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", value);
- return -1;
- }
- return 0;
- }
+ continue;
+ } else if (strcasecmp(c->key, "PluginInstanceFormat") == 0) {
+ /* this option can handle list of string parameters in one line*/
+ if (check_config_multiple_string_entry(c) != 0) {
+ ERROR(PLUGIN_NAME
+ " plugin: Could not get 'PluginInstanceFormat' parameter");
+ return -1;
+ }
- if (strcasecmp(key, "Instances") == 0) {
- char *eptr = NULL;
- double val = strtod(value, &eptr);
+ const int params_num = c->values_num;
+ for (int i = 0; i < params_num; ++i) {
+ const char *param_name = c->values[i].value.string;
+ if (strcasecmp(param_name, "none") == 0) {
+ plugin_instance_format[i] = plginst_none;
+ break;
+ } else if (strcasecmp(param_name, "name") == 0)
+ plugin_instance_format[i] = plginst_name;
+ else if (strcasecmp(param_name, "uuid") == 0)
+ plugin_instance_format[i] = plginst_uuid;
+ else if (strcasecmp(param_name, "metadata") == 0)
+ plugin_instance_format[i] = plginst_metadata;
+ else {
+ ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
+ param_name);
+
+ return -1;
+ }
+ }
- if (*eptr != '\0') {
- ERROR(PLUGIN_NAME " plugin: Invalid value for Instances = '%s'", value);
- return 1;
- }
- if (val <= 0) {
- ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
- return 1;
- }
- if (val > NR_INSTANCES_MAX) {
- ERROR(PLUGIN_NAME " plugin: Instances=%f > NR_INSTANCES_MAX=%i"
- " use a lower setting or recompile the plugin.",
- val, NR_INSTANCES_MAX);
- return 1;
- }
+ for (int i = params_num; i < PLGINST_MAX_FIELDS; ++i)
+ plugin_instance_format[i] = plginst_none;
- nr_instances = (int)val;
- DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
- return 0;
- }
+ continue;
+ } else if (strcasecmp(c->key, "InterfaceFormat") == 0) {
+ char *format = NULL;
+ if (cf_util_get_string(c, &format) != 0)
+ return -1;
+
+ if (strcasecmp(format, "name") == 0)
+ interface_format = if_name;
+ else if (strcasecmp(format, "address") == 0)
+ interface_format = if_address;
+ else if (strcasecmp(format, "number") == 0)
+ interface_format = if_number;
+ else {
+ ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", format);
+ sfree(format);
+ return -1;
+ }
+
+ sfree(format);
+ continue;
+ } else if (strcasecmp(c->key, "Instances") == 0) {
+ if (cf_util_get_int(c, &nr_instances) != 0)
+ return -1;
+
+ if (nr_instances <= 0) {
+ ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
+ return -1;
+ }
+ if (nr_instances > NR_INSTANCES_MAX) {
+ ERROR(PLUGIN_NAME " plugin: Instances=%i > NR_INSTANCES_MAX=%i"
+ " use a lower setting or recompile the plugin.",
+ nr_instances, NR_INSTANCES_MAX);
+ return -1;
+ }
+
+ DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
+ continue;
+ } else if (strcasecmp(c->key, "ExtraStats") == 0) {
+ char *ex_str = NULL;
+
+ if (cf_util_get_string(c, &ex_str) != 0)
+ return -1;
- if (strcasecmp(key, "ExtraStats") == 0) {
- char *localvalue = strdup(value);
- if (localvalue != NULL) {
char *exstats[EX_STATS_MAX_FIELDS];
- int numexstats =
- strsplit(localvalue, exstats, STATIC_ARRAY_SIZE(exstats));
- extra_stats = parse_ex_stats_flags(exstats, numexstats);
- sfree(localvalue);
+ int numexstats = strsplit(ex_str, exstats, STATIC_ARRAY_SIZE(exstats));
+ int status = parse_ex_stats_flags(&extra_stats, exstats, numexstats);
+ sfree(ex_str);
+ if (status != 0) {
+ ERROR(PLUGIN_NAME " plugin: parsing 'ExtraStats' option failed");
+ return status;
+ }
#ifdef HAVE_JOB_STATS
if ((extra_stats & ex_stats_job_stats_completed) &&
ERROR(PLUGIN_NAME " plugin: Invalid job stats configuration. Only one "
"type of job statistics can be collected at the same "
"time");
- return 1;
+ return -1;
}
#endif
- }
- }
- if (strcasecmp(key, "PersistentNotification") == 0) {
- persistent_notification = IS_TRUE(value);
- return 0;
- }
+ /* ExtraStats parsed successfully */
+ continue;
+ } else if (strcasecmp(c->key, "PersistentNotification") == 0) {
+ if (cf_util_get_boolean(c, &persistent_notification) != 0)
+ return -1;
- if (strcasecmp(key, "ReportBlockDevices") == 0) {
- report_block_devices = IS_TRUE(value);
- return 0;
- }
+ continue;
+ } else if (strcasecmp(c->key, "ReportBlockDevices") == 0) {
+ if (cf_util_get_boolean(c, &report_block_devices) != 0)
+ return -1;
- if (strcasecmp(key, "ReportNetworkInterfaces") == 0) {
- report_network_interfaces = IS_TRUE(value);
- return 0;
+ continue;
+ } else if (strcasecmp(c->key, "ReportNetworkInterfaces") == 0) {
+ if (cf_util_get_boolean(c, &report_network_interfaces) != 0)
+ return -1;
+
+ continue;
+ } else {
+ /* Unrecognised option. */
+ ERROR(PLUGIN_NAME " plugin: Unrecognized option: '%s'", c->key);
+ return -1;
+ }
}
- /* Unrecognised option. */
- return -1;
+ return 0;
}
static int lv_connect(void) {
if (conn == NULL) {
+ /* event implementation must be registered before connection is opened */
+ if (!persistent_notification)
+ if (register_event_impl() != 0)
+ return -1;
+
/* `conn_string == NULL' is acceptable */
#ifdef HAVE_FS_INFO
/* virDomainGetFSInfo requires full read-write access connection */
int status = virNodeGetInfo(conn, &nodeinfo);
if (status != 0) {
ERROR(PLUGIN_NAME " plugin: virNodeGetInfo failed");
+ virConnectClose(conn);
+ conn = NULL;
return -1;
}
+
+ if (!persistent_notification)
+ if (start_event_loop(¬if_thread) != 0) {
+ virConnectClose(conn);
+ conn = NULL;
+ return -1;
+ }
}
c_release(LOG_NOTICE, &conn_complain,
PLUGIN_NAME " plugin: Connection established.");
#endif /* HAVE_CPU_STATS */
#ifdef HAVE_DOM_REASON
-
-static void domain_state_submit(virDomainPtr dom, int state, int reason) {
- value_t values[] = {
- {.gauge = (gauge_t)state}, {.gauge = (gauge_t)reason},
- };
-
- submit(dom, "domain_state", NULL, values, STATIC_ARRAY_SIZE(values));
-}
-
-static int get_domain_state(virDomainPtr domain) {
+static int submit_domain_state(virDomainPtr domain) {
int domain_state = 0;
int domain_reason = 0;
return status;
}
- domain_state_submit(domain, domain_state, domain_reason);
+ value_t values[] = {
+ {.gauge = (gauge_t)domain_state}, {.gauge = (gauge_t)domain_reason},
+ };
- return status;
+ submit(domain, "domain_state", NULL, values, STATIC_ARRAY_SIZE(values));
+
+ return 0;
}
#ifdef HAVE_LIST_ALL_DOMAINS
return status;
}
- if (persistent_notification)
- domain_state_submit_notif(domain, domain_state, domain_reason);
+ domain_state_submit_notif(domain, domain_state, domain_reason);
return status;
}
return mem_stats;
}
- for (int i = 0; i < mem_stats; i++)
- memory_stats_submit((gauge_t)minfo[i].val * 1024, domain, minfo[i].tag);
+ derive_t swap_in = -1;
+ derive_t swap_out = -1;
+ derive_t min_flt = -1;
+ derive_t maj_flt = -1;
+
+ for (int i = 0; i < mem_stats; i++) {
+ if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_IN)
+ swap_in = minfo[i].val;
+ else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_OUT)
+ swap_out = minfo[i].val;
+ else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_MINOR_FAULT)
+ min_flt = minfo[i].val;
+ else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_MAJOR_FAULT)
+ maj_flt = minfo[i].val;
+#ifdef LIBVIR_CHECK_VERSION
+#if LIBVIR_CHECK_VERSION(2, 1, 0)
+ else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_LAST_UPDATE)
+ /* Skip 'last_update' reporting as that is not memory but timestamp */
+ continue;
+#endif
+#endif
+ else
+ memory_stats_submit((gauge_t)minfo[i].val * 1024, domain, minfo[i].tag);
+ }
+
+ if (swap_in > 0 || swap_out > 0) {
+ submit(domain, "swap_io", "in", &(value_t){.gauge = swap_in}, 1);
+ submit(domain, "swap_io", "out", &(value_t){.gauge = swap_out}, 1);
+ }
+
+ if (min_flt > 0 || maj_flt > 0) {
+ value_t values[] = {
+ {.gauge = (gauge_t)min_flt}, {.gauge = (gauge_t)maj_flt},
+ };
+ submit(domain, "ps_pagefaults", NULL, values, STATIC_ARRAY_SIZE(values));
+ }
sfree(minfo);
return 0;
* however it doesn't provide a reason for entering particular state.
* We need to get it from virDomainGetState.
*/
- GET_STATS(get_domain_state, "domain reason", domain->ptr);
+ GET_STATS(submit_domain_state, "domain reason", domain->ptr);
#endif
}
}
static int virt_notif_thread_init(virt_notif_thread_t *thread_data) {
- int ret;
-
assert(thread_data != NULL);
- ret = pthread_mutex_init(&thread_data->active_mutex, NULL);
+
+ int ret = pthread_mutex_init(&thread_data->active_mutex, NULL);
if (ret != 0) {
ERROR(PLUGIN_NAME " plugin: Failed to initialize mutex, err %u", ret);
return ret;
return -1;
}
+ DEBUG(PLUGIN_NAME " plugin: starting event loop");
+
virt_notif_thread_set_active(thread_data, 1);
if (pthread_create(&thread_data->event_loop_tid, NULL, event_loop_worker,
thread_data)) {
ERROR(PLUGIN_NAME " plugin: failed event loop thread creation");
+ virt_notif_thread_set_active(thread_data, 0);
virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
+ thread_data->domain_event_cb_id = -1;
return -1;
}
/* stop event loop thread and deregister callback */
static void stop_event_loop(virt_notif_thread_t *thread_data) {
- /* stopping loop and de-registering event handler*/
- virt_notif_thread_set_active(thread_data, 0);
- if (conn != NULL && thread_data->domain_event_cb_id != -1)
- virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
- if (pthread_join(notif_thread.event_loop_tid, NULL) != 0)
- ERROR(PLUGIN_NAME " plugin: stopping notification thread failed");
+ DEBUG(PLUGIN_NAME " plugin: stopping event loop");
+
+ /* Stopping loop */
+ if (virt_notif_thread_is_active(thread_data)) {
+ virt_notif_thread_set_active(thread_data, 0);
+ if (pthread_join(notif_thread.event_loop_tid, NULL) != 0)
+ ERROR(PLUGIN_NAME " plugin: stopping notification thread failed");
+ }
+
+ /* ... and de-registering event handler */
+ if (conn != NULL && thread_data->domain_event_cb_id != -1) {
+ virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
+ thread_data->domain_event_cb_id = -1;
+ }
}
static int persistent_domains_state_notification(void) {
}
static int lv_read(user_data_t *ud) {
- time_t t;
- struct lv_read_instance *inst = NULL;
- struct lv_read_state *state = NULL;
-
if (ud->data == NULL) {
ERROR(PLUGIN_NAME " plugin: NULL userdata");
return -1;
}
- inst = ud->data;
- state = &inst->read_state;
-
- bool reconnect = conn == NULL ? true : false;
- /* event implementation must be registered before connection is opened */
- if (inst->id == 0) {
- if (!persistent_notification && reconnect)
- if (register_event_impl() != 0)
- return -1;
+ struct lv_read_instance *inst = ud->data;
+ struct lv_read_state *state = &inst->read_state;
+ if (inst->id == 0)
if (lv_connect() < 0)
return -1;
- if (!persistent_notification && reconnect && conn != NULL)
- if (start_event_loop(¬if_thread) != 0)
- return -1;
+ /* Wait until inst#0 establish connection */
+ if (conn == NULL) {
+ DEBUG(PLUGIN_NAME " plugin#%s: Wait until inst#0 establish connection",
+ inst->tag);
+ return 0;
}
+ time_t t;
time(&t);
/* Need to refresh domain or device lists? */
if (dom->active)
status = get_domain_metrics(dom);
#ifdef HAVE_DOM_REASON
- else
- status = get_domain_state(dom->ptr);
+ else if (extra_stats & ex_stats_domain_state)
+ status = submit_domain_state(dom->ptr);
#endif
if (status != 0)
if (lv_init_ignorelists() != 0)
return -1;
- /* event implementation must be registered before connection is opened */
if (!persistent_notification)
- if (register_event_impl() != 0)
+ if (virt_notif_thread_init(¬if_thread) != 0)
return -1;
- if (lv_connect() != 0)
- return -1;
-
- DEBUG(PLUGIN_NAME " plugin: starting event loop");
-
- if (!persistent_notification) {
- virt_notif_thread_init(¬if_thread);
- if (start_event_loop(¬if_thread) != 0)
- return -1;
- }
+ lv_connect();
DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
char *path = NULL;
char *address = NULL;
+ const int itf_number = j + 1;
xmlNodePtr xml_interface = xml_interfaces->nodeTab[j];
if (!xml_interface)
}
}
- if ((ignore_device_match(il_interface_devices, domname, path) == 0 &&
- ignore_device_match(il_interface_devices, domname, address) == 0)) {
- add_interface_device(state, dom, path, address, j + 1);
+ bool device_ignored = false;
+ switch (interface_format) {
+ case if_name:
+ if (ignore_device_match(il_interface_devices, domname, path) != 0)
+ device_ignored = true;
+ break;
+ case if_address:
+ if (ignore_device_match(il_interface_devices, domname, address) != 0)
+ device_ignored = true;
+ break;
+ case if_number: {
+ char number_string[4];
+ snprintf(number_string, sizeof(number_string), "%d", itf_number);
+ if (ignore_device_match(il_interface_devices, domname, number_string) !=
+ 0)
+ device_ignored = true;
+ } break;
+ default:
+ ERROR(PLUGIN_NAME " plugin: Unknown interface_format option: %d",
+ interface_format);
}
+ if (!device_ignored)
+ add_interface_device(state, dom, path, address, itf_number);
+
if (path)
xmlFree(path);
if (address)
xmlXPathFreeObject(xpath_obj);
}
+static bool is_domain_ignored(virDomainPtr dom) {
+ const char *domname = virDomainGetName(dom);
+
+ if (domname == NULL) {
+ VIRT_ERROR(conn, "virDomainGetName failed, ignoring domain");
+ return true;
+ }
+
+ if (ignorelist_match(il_domains, domname) != 0) {
+ DEBUG(PLUGIN_NAME
+ " plugin: ignoring domain '%s' because of ignorelist option",
+ domname);
+ return true;
+ }
+
+ return false;
+}
+
static int refresh_lists(struct lv_read_instance *inst) {
struct lv_read_state *state = &inst->read_state;
int n;
#ifdef HAVE_LIST_ALL_DOMAINS
for (int i = 0; i < m; ++i)
- if (add_domain(state, domains_inactive[i], 0) < 0) {
- ERROR(PLUGIN_NAME " plugin: malloc failed.");
+ if (is_domain_ignored(domains_inactive[i]) ||
+ add_domain(state, domains_inactive[i], 0) < 0) {
+ /* domain ignored or failed during adding to domains list*/
virDomainFree(domains_inactive[i]);
domains_inactive[i] = NULL;
continue;
}
#endif
- if (add_domain(state, dom, 1) < 0) {
+ if (is_domain_ignored(dom) || add_domain(state, dom, 1) < 0) {
/*
+ * domain ignored or failed during adding to domains list
+ *
* When domain is already tracked, then there is
* no problem with memory handling (will be freed
* with the rest of domains cached data)
* before adding domain to track) we have to take
* care it ourselves and call virDomainFree
*/
- ERROR(PLUGIN_NAME " plugin: malloc failed.");
virDomainFree(dom);
continue;
}
continue;
}
- if (ignorelist_match(il_domains, domname) != 0)
- continue;
-
/* Get a list of devices for this domain. */
xmlDocPtr xml_doc = NULL;
xmlXPathContextPtr xpath_ctx = NULL;
static int add_domain(struct lv_read_state *state, virDomainPtr dom,
bool active) {
-
int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
domain_t *new_ptr = realloc(state->domains, new_size);
- if (new_ptr == NULL)
+ if (new_ptr == NULL) {
+ ERROR(PLUGIN_NAME " plugin: realloc failed in add_domain()");
return -1;
+ }
state->domains = new_ptr;
state->domains[state->nr_domains].ptr = dom;
lv_fini_instance(i);
}
- DEBUG(PLUGIN_NAME " plugin: stopping event loop");
-
if (!persistent_notification)
stop_event_loop(¬if_thread);
}
void module_register(void) {
- plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
+ plugin_register_complex_config("virt", lv_config);
plugin_register_init(PLUGIN_NAME, lv_init);
plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
}
* 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>
*/
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 {
--- /dev/null
+/**
+ * collectd - src/write_syslog.c
+ * Copyright (C) 2012 Pierre-Yves Ritschard
+ * Copyright (C) 2011 Scott Sanders
+ * Copyright (C) 2009 Paul Sadauskas
+ * Copyright (C) 2009 Doug MacEachern
+ * Copyright (C) 2007-2012 Florian octo Forster
+ * Copyright (C) 2013-2014 Limelight Networks, Inc.
+ * Copyright (C) 2019 Shirly Radco
+ * 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
+ *
+ * Based on the write_graphite plugin. Authors:
+ * Florian octo Forster <octo at collectd.org>
+ * Doug MacEachern <dougm at hyperic.com>
+ * Paul Sadauskas <psadauskas at gmail.com>
+ * Scott Sanders <scott at jssjr.com>
+ * Pierre-Yves Ritschard <pyr at spootnik.org>
+ * Based on the write_tsdb plugin. Authors:
+ * Brett Hawn <bhawn at llnw.com>
+ * Kevin Bowling <kbowling@llnw.com>
+ * write_syslog. Authors:
+ * Shirly Radco <sradco@redhat.com>
+ **/
+
+/* write_syslog plugin configuration example
+ *
+ * <Plugin write_syslog>
+ * <Node>
+ * Host "localhost"
+ * Port "44514"
+ * Prefix "collectd"
+ * MessageFormat "human"
+ * HostTags "["prefix1" "example1"="example1_v"]
+ * </Node>
+ * </Plugin>
+ *
+ */
+
+#include "collectd.h"
+#include "utils/common/common.h"
+
+#include "plugin.h"
+#include "utils_cache.h"
+#include "utils_random.h"
+
+#include <netdb.h>
+
+#define WS_DEFAULT_NODE "localhost"
+
+#define WS_DEFAULT_SERVICE "44514"
+
+#define WS_DEFAULT_FORMAT "human"
+
+#define WS_DEFAULT_PREFIX "collectd"
+
+#define WS_DEFAULT_ESCAPE '.'
+
+/* Ethernet - (IPv6 + TCP) = 1500 - (40 + 32) = 1428 */
+#define WS_SEND_BUF_SIZE 1428
+
+/*
+ * Private variables
+ */
+struct ws_callback {
+ struct addrinfo *ai;
+ cdtime_t ai_last_update;
+ int sock_fd;
+
+ char *node;
+ char *service;
+ char *host_tags;
+ char *msg_format;
+ char *metrics_prefix;
+ bool store_rates;
+ bool always_append_ds;
+
+ char send_buf[WS_SEND_BUF_SIZE];
+ size_t send_buf_free;
+ size_t send_buf_fill;
+ cdtime_t send_buf_init_time;
+
+ pthread_mutex_t send_lock;
+
+ bool connect_failed_log_enabled;
+ int connect_dns_failed_attempts_remaining;
+ cdtime_t next_random_ttl;
+};
+
+static cdtime_t resolve_interval;
+static cdtime_t resolve_jitter;
+
+/*
+ * Functions
+ */
+static void ws_reset_buffer(struct ws_callback *cb) {
+ memset(cb->send_buf, 0, sizeof(cb->send_buf));
+ cb->send_buf_free = sizeof(cb->send_buf);
+ cb->send_buf_fill = 0;
+ cb->send_buf_init_time = cdtime();
+}
+
+static int ws_send_buffer(struct ws_callback *cb) {
+ ssize_t status = 0;
+
+ status = swrite(cb->sock_fd, cb->send_buf, strlen(cb->send_buf));
+ if (status != 0) {
+ ERROR("write_syslog plugin: send failed with status %zi (%s)", status,
+ STRERRNO);
+
+ if (cb->sock_fd > 0) {
+ close(cb->sock_fd);
+ cb->sock_fd = -1;
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+
+/* NOTE: You must hold cb->send_lock when calling this function! */
+static int ws_flush_nolock(cdtime_t timeout, struct ws_callback *cb) {
+ int status;
+
+ DEBUG("write_syslog plugin: ws_flush_nolock: timeout = %.3f; "
+ "send_buf_fill = %" PRIsz ";",
+ (double)timeout, cb->send_buf_fill);
+
+ /* timeout == 0 => flush unconditionally */
+ if (timeout > 0) {
+ cdtime_t now;
+
+ now = cdtime();
+ if ((cb->send_buf_init_time + timeout) > now)
+ return 0;
+ }
+
+ if (cb->send_buf_fill == 0) {
+ cb->send_buf_init_time = cdtime();
+ return 0;
+ }
+
+ status = ws_send_buffer(cb);
+ ws_reset_buffer(cb);
+
+ return status;
+}
+
+static cdtime_t new_random_ttl(void) {
+ if (resolve_jitter == 0)
+ return 0;
+
+ return (cdtime_t)cdrand_range(0, (long)resolve_jitter);
+}
+
+static int ws_callback_init(struct ws_callback *cb) {
+ int status;
+ cdtime_t now;
+
+ const char *node = cb->node ? cb->node : WS_DEFAULT_NODE;
+ const char *service = cb->service ? cb->service : WS_DEFAULT_SERVICE;
+
+ if (cb->sock_fd > 0)
+ return 0;
+
+ now = cdtime();
+ if (cb->ai) {
+ /* When we are here, we still have the IP in cache.
+ * If we have remaining attempts without calling the DNS, we update the
+ * last_update date so we keep the info until next time.
+ * If there is no more attempts, we need to flush the cache.
+ */
+
+ if ((cb->ai_last_update + resolve_interval + cb->next_random_ttl) < now) {
+ cb->next_random_ttl = new_random_ttl();
+ if (cb->connect_dns_failed_attempts_remaining > 0) {
+ /* Warning : this is run under send_lock mutex.
+ * This is why we do not use another mutex here.
+ * */
+ cb->ai_last_update = now;
+ cb->connect_dns_failed_attempts_remaining--;
+ } else {
+ freeaddrinfo(cb->ai);
+ cb->ai = NULL;
+ }
+ }
+ }
+
+ if (cb->ai == NULL) {
+ if ((cb->ai_last_update + resolve_interval + cb->next_random_ttl) >= now) {
+ DEBUG("write_syslog plugin: too many getaddrinfo(%s, %s) failures", node,
+ service);
+ return -1;
+ }
+ cb->ai_last_update = now;
+ cb->next_random_ttl = new_random_ttl();
+
+ struct addrinfo ai_hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_flags = AI_ADDRCONFIG,
+ .ai_socktype = SOCK_STREAM,
+ };
+
+ status = getaddrinfo(node, service, &ai_hints, &cb->ai);
+ if (status != 0) {
+ if (cb->ai) {
+ freeaddrinfo(cb->ai);
+ cb->ai = NULL;
+ }
+ if (cb->connect_failed_log_enabled) {
+ ERROR("write_syslog plugin: getaddrinfo(%s, %s) failed: %s", node,
+ service, gai_strerror(status));
+ cb->connect_failed_log_enabled = 0;
+ }
+ return -1;
+ }
+ }
+
+ assert(cb->ai != NULL);
+ for (struct addrinfo *ai = cb->ai; ai != NULL; ai = ai->ai_next) {
+ cb->sock_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (cb->sock_fd < 0)
+ continue;
+
+ set_sock_opts(cb->sock_fd);
+
+ status = connect(cb->sock_fd, ai->ai_addr, ai->ai_addrlen);
+ if (status != 0) {
+ close(cb->sock_fd);
+ cb->sock_fd = -1;
+ continue;
+ }
+
+ break;
+ }
+
+ if (cb->sock_fd < 0) {
+ ERROR("write_syslog plugin: Connecting to %s:%s failed. "
+ "The last error was: %s",
+ node, service, STRERRNO);
+ return -1;
+ }
+
+ if (cb->connect_failed_log_enabled == 0) {
+ INFO("write_syslog plugin: Connecting to %s:%s succeeded.", node, service);
+ cb->connect_failed_log_enabled = 1;
+ }
+ cb->connect_dns_failed_attempts_remaining = 1;
+
+ ws_reset_buffer(cb);
+
+ return 0;
+}
+
+static void ws_callback_free(void *data) {
+ struct ws_callback *cb;
+
+ if (data == NULL)
+ return;
+
+ cb = data;
+
+ pthread_mutex_lock(&cb->send_lock);
+
+ ws_flush_nolock(0, cb);
+
+ close(cb->sock_fd);
+ cb->sock_fd = -1;
+
+ sfree(cb->node);
+ sfree(cb->service);
+ sfree(cb->host_tags);
+ sfree(cb->msg_format);
+ sfree(cb->metrics_prefix);
+
+ pthread_mutex_unlock(&cb->send_lock);
+ pthread_mutex_destroy(&cb->send_lock);
+
+ sfree(cb);
+}
+
+static int ws_flush(cdtime_t timeout,
+ const char *identifier __attribute__((unused)),
+ user_data_t *user_data) {
+ struct ws_callback *cb;
+ int status;
+
+ if (user_data == NULL)
+ return -EINVAL;
+
+ cb = user_data->data;
+
+ pthread_mutex_lock(&cb->send_lock);
+
+ if (cb->sock_fd < 0) {
+ status = ws_callback_init(cb);
+ if (status != 0) {
+ ERROR("write_syslog plugin: ws_callback_init failed.");
+ pthread_mutex_unlock(&cb->send_lock);
+ return -1;
+ }
+ }
+
+ status = ws_flush_nolock(timeout, cb);
+ pthread_mutex_unlock(&cb->send_lock);
+
+ return status;
+}
+
+static int ws_format_values(char *ret, size_t ret_len, int ds_num,
+ const data_set_t *ds, const value_list_t *vl,
+ bool store_rates) {
+ size_t offset = 0;
+ int status;
+ gauge_t *rates = NULL;
+
+ assert(strcmp(ds->type, vl->type) == 0);
+
+ memset(ret, 0, ret_len);
+
+#define BUFFER_ADD(...) \
+ do { \
+ status = snprintf(ret + offset, ret_len - offset, __VA_ARGS__); \
+ if (status < 1) { \
+ sfree(rates); \
+ return -1; \
+ } else if (((size_t)status) >= (ret_len - offset)) { \
+ sfree(rates); \
+ 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 (store_rates) {
+ if (rates == NULL)
+ rates = uc_get_rate(ds, vl);
+ if (rates == NULL) {
+ WARNING("format_values: "
+ "uc_get_rate failed.");
+ return -1;
+ }
+ BUFFER_ADD(GAUGE_FORMAT, 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 {
+ ERROR("format_values plugin: Unknown data source type: %i",
+ ds->ds[ds_num].type);
+ sfree(rates);
+ return -1;
+ }
+
+#undef BUFFER_ADD
+
+ sfree(rates);
+ return 0;
+}
+
+static int ws_format_name(char *ret, int ret_len, const value_list_t *vl,
+ const struct ws_callback *cb, const char *ds_name) {
+
+ if (ds_name != NULL) {
+ snprintf(ret, ret_len, "%s.%s", vl->type, ds_name);
+ } else { /* ds_name == NULL */
+ snprintf(ret, ret_len, "%s", vl->type);
+ }
+
+ return 0;
+}
+
+static int ws_send_message(const char *key, const char *value, cdtime_t time,
+ struct ws_callback *cb, const char *plugin,
+ const char *plugin_instance,
+ const char *type_instance, const char *type,
+ const char *ds_name, cdtime_t interval,
+ const char *host) {
+ int status;
+ size_t message_len;
+ char message[1024];
+ char rfc3339_timestamp[64];
+ const char *host_tags = cb->host_tags ? cb->host_tags : "";
+ const char *host_tags_json_prefix = "";
+ const char *metrics_prefix =
+ cb->metrics_prefix ? cb->metrics_prefix : WS_DEFAULT_PREFIX;
+ const char *msg_format = cb->msg_format ? cb->msg_format : WS_DEFAULT_FORMAT;
+ int pid;
+
+ pid = getpid();
+
+ rfc3339_local(rfc3339_timestamp, sizeof(rfc3339_timestamp), time);
+
+ /* skip if value is NaN */
+ if (value[0] == 'n')
+ return 0;
+
+ if (strcasecmp("JSON", msg_format) == 0) {
+ if (cb->host_tags) {
+ host_tags_json_prefix = ",";
+ }
+ status = snprintf(
+ /* The metric key-values are are part of the syslog msg, in json
+ format */
+ message, sizeof(message),
+ "<166>1 %s %s collectd %d - - {\"time\":%.0f, \"%s\":{ \"%s\":{ "
+ "\"%s\":%s }, "
+ "\"plugin\":\"%s\", \"plugin_instance\":\"%s\", "
+ "\"type_instance\":\"%s\","
+ " \"type\":\"%s\", \"interval\":%.0f }, \"hostname\":\"%s\" %s "
+ "%s}\n",
+ rfc3339_timestamp, host, pid, CDTIME_T_TO_DOUBLE(time), metrics_prefix,
+ plugin, key, value, plugin, plugin_instance, type_instance, type,
+ CDTIME_T_TO_DOUBLE(interval), host, host_tags_json_prefix, host_tags);
+ } else {
+ status = snprintf(
+ /* The metric key-values are part of the syslog structrude data,
+ * MessageFormat = "human" */
+ message, sizeof(message),
+ "<166>1 %s %s collectd %d - [%s value=\"%s\""
+ " plugin=\"%s\" plugin_instance=\"%s\""
+ " type_instance=\"%s\" type=\"%s\""
+ " ds_name=\"%s\" interval=\"%.0f\"] %s %s.%s=\"%s\"\n",
+ rfc3339_timestamp, host, pid, metrics_prefix, value, plugin,
+ plugin_instance, type_instance, type, ds_name,
+ CDTIME_T_TO_DOUBLE(interval), host_tags, plugin, key, value);
+ }
+ if (status < 0)
+ return -1;
+ message_len = (size_t)status;
+
+ if (message_len >= sizeof(message)) {
+ ERROR("write_syslog plugin: message buffer too small: "
+ "Need %" PRIsz " bytes.",
+ message_len + 1);
+ return -1;
+ }
+
+ pthread_mutex_lock(&cb->send_lock);
+
+ if (cb->sock_fd < 0) {
+ status = ws_callback_init(cb);
+ if (status != 0) {
+ ERROR("write_syslog plugin: ws_callback_init failed.");
+ pthread_mutex_unlock(&cb->send_lock);
+ return -1;
+ }
+ }
+
+ if (message_len >= cb->send_buf_free) {
+ status = ws_flush_nolock(0, cb);
+ if (status != 0) {
+ pthread_mutex_unlock(&cb->send_lock);
+ return status;
+ }
+ }
+
+ /* Assert that we have enough space for this message. */
+ assert(message_len < cb->send_buf_free);
+
+ /* `message_len + 1' because `message_len' does not include the
+ * trailing null byte. Neither does `send_buffer_fill'. */
+ memcpy(cb->send_buf + cb->send_buf_fill, message, message_len + 1);
+ cb->send_buf_fill += message_len;
+ cb->send_buf_free -= message_len;
+
+ DEBUG("write_syslog plugin: [%s]:%s buf %" PRIsz "/%" PRIsz
+ " (%.1f %%) \"%s\"",
+ cb->node, cb->service, cb->send_buf_fill, sizeof(cb->send_buf),
+ 100.0 * ((double)cb->send_buf_fill) / ((double)sizeof(cb->send_buf)),
+ message);
+
+ pthread_mutex_unlock(&cb->send_lock);
+
+ return 0;
+}
+
+static int ws_write_messages(const data_set_t *ds, const value_list_t *vl,
+ struct ws_callback *cb) {
+ char key[10 * DATA_MAX_NAME_LEN];
+ char values[512];
+
+ int status;
+
+ if (0 != strcmp(ds->type, vl->type)) {
+ ERROR("write_syslog plugin: DS type does not match "
+ "value list type");
+ return -1;
+ }
+
+ for (size_t i = 0; i < ds->ds_num; i++) {
+ const char *ds_name = NULL;
+
+ if (cb->always_append_ds || (ds->ds_num > 1))
+ ds_name = ds->ds[i].name;
+
+ /* Copy the identifier to 'key' and escape it. */
+ status = ws_format_name(key, sizeof(key), vl, cb, ds_name);
+ if (status != 0) {
+ ERROR("write_syslog plugin: error with format_name");
+ return status;
+ }
+
+ escape_string(key, sizeof(key));
+ /* Convert the values to an ASCII representation and put that into
+ * 'values'. */
+ status =
+ ws_format_values(values, sizeof(values), i, ds, vl, cb->store_rates);
+ if (status != 0) {
+ ERROR("write_syslog plugin: error with "
+ "ws_format_values");
+ return status;
+ }
+
+ /* Send the message to tcp */
+ status = ws_send_message(key, values, vl->time, cb, vl->plugin,
+ vl->plugin_instance, vl->type_instance, vl->type,
+ ds_name, vl->interval, vl->host);
+ if (status != 0) {
+ ERROR("write_syslog plugin: error with "
+ "ws_send_message");
+ return status;
+ }
+ }
+
+ return 0;
+}
+
+static int ws_write(const data_set_t *ds, const value_list_t *vl,
+ user_data_t *user_data) {
+ struct ws_callback *cb;
+ int status;
+
+ if (user_data == NULL)
+ return EINVAL;
+
+ cb = user_data->data;
+
+ status = ws_write_messages(ds, vl, cb);
+
+ return status;
+}
+
+static int ws_config_tsd(oconfig_item_t *ci) {
+ struct ws_callback *cb;
+ char callback_name[DATA_MAX_NAME_LEN];
+
+ cb = calloc(1, sizeof(*cb));
+ if (cb == NULL) {
+ ERROR("write_syslog plugin: calloc failed.");
+ return -1;
+ }
+ cb->sock_fd = -1;
+ cb->connect_failed_log_enabled = 1;
+ cb->next_random_ttl = new_random_ttl();
+
+ pthread_mutex_init(&cb->send_lock, NULL);
+
+ for (int i = 0; i < ci->children_num; i++) {
+ oconfig_item_t *child = ci->children + i;
+
+ if (strcasecmp("Host", child->key) == 0)
+ cf_util_get_string(child, &cb->node);
+ else if (strcasecmp("Port", child->key) == 0)
+ cf_util_get_service(child, &cb->service);
+ else if (strcasecmp("MessageFormat", child->key) == 0)
+ cf_util_get_string(child, &cb->msg_format);
+ else if (strcasecmp("HostTags", child->key) == 0)
+ cf_util_get_string(child, &cb->host_tags);
+ else if (strcasecmp("StoreRates", child->key) == 0)
+ cf_util_get_boolean(child, &cb->store_rates);
+ else if (strcasecmp("AlwaysAppendDS", child->key) == 0)
+ cf_util_get_boolean(child, &cb->always_append_ds);
+ else if (strcasecmp("Prefix", child->key) == 0)
+ cf_util_get_string(child, &cb->metrics_prefix);
+ else {
+ ERROR("write_syslog plugin: Invalid configuration "
+ "option: %s.",
+ child->key);
+ return -1;
+ }
+ }
+
+ snprintf(callback_name, sizeof(callback_name), "write_syslog/%s/%s",
+ cb->node != NULL ? cb->node : WS_DEFAULT_NODE,
+ cb->service != NULL ? cb->service : WS_DEFAULT_SERVICE);
+
+ user_data_t user_data = {.data = cb, .free_func = ws_callback_free};
+
+ plugin_register_write(callback_name, ws_write, &user_data);
+
+ user_data.free_func = NULL;
+ plugin_register_flush(callback_name, ws_flush, &user_data);
+
+ return 0;
+}
+
+static int ws_config(oconfig_item_t *ci) {
+ if ((resolve_interval == 0) && (resolve_jitter == 0))
+ resolve_interval = resolve_jitter = plugin_get_interval();
+
+ for (int i = 0; i < ci->children_num; i++) {
+ oconfig_item_t *child = ci->children + i;
+
+ if (strcasecmp("Node", child->key) == 0) {
+ if (ws_config_tsd(child) < 0)
+ return -1;
+ } else if (strcasecmp("ResolveInterval", child->key) == 0)
+ cf_util_get_cdtime(child, &resolve_interval);
+ else if (strcasecmp("ResolveJitter", child->key) == 0)
+ cf_util_get_cdtime(child, &resolve_jitter);
+ else {
+ ERROR("write_syslog plugin: Invalid configuration "
+ "option: %s.",
+ child->key);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void module_register(void) {
+ plugin_register_complex_config("write_syslog", ws_config);
+}