Merge branch 'master' into feat_mcelog_notification_bugfixes
authorRoman Korynkevych <romanx.korynkevych@intel.com>
Fri, 14 Jul 2017 14:26:44 +0000 (17:26 +0300)
committerGitHub <noreply@github.com>
Fri, 14 Jul 2017 14:26:44 +0000 (17:26 +0300)
1  2 
src/collectd.conf.in
src/collectd.conf.pod
src/mcelog.c

diff --combined src/collectd.conf.in
  #@BUILD_PLUGIN_SIGROK_TRUE@LoadPlugin sigrok
  #@BUILD_PLUGIN_SMART_TRUE@LoadPlugin smart
  #@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
+ #@BUILD_PLUGIN_SNMP_AGENT_TRUE@LoadPlugin snmp_agent
  #@BUILD_PLUGIN_STATSD_TRUE@LoadPlugin statsd
  #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
  #@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
  #  <EAL>
  #    Coremask "0x1"
  #    MemoryChannels "4"
- #    ProcessType "secondary"
  #    FilePrefix "rte"
  #  </EAL>
  #  <Event "link_status">
  #  <EAL>
  #    Coremask "0x2"
  #    MemoryChannels "4"
- #    ProcessType "secondary"
  #    FilePrefix "rte"
  #  </EAL>
  #  SharedMemObj "dpdk_collectd_stats_0"
  #</Plugin>
  
  #<Plugin mcelog>
 -#     McelogClientSocket "/var/run/mcelog-client"
 -#     McelogLogfile "/var/log/mcelog"
 +#  <Memory>
 +#    McelogClientSocket "/var/run/mcelog-client"
 +#    PersistentNotification false
 +#  </Memory>
 +#  McelogLogfile "/var/log/mcelog"
  #</Plugin>
  
  #<Plugin md>
  #   </Host>
  #</Plugin>
  
+ #<Plugin snmp_agent>
+ #  <Data "memAvailReal">
+ #    Plugin "memory"
+ #    Type "memory"
+ #    TypeInstance "free"
+ #    OIDs "1.3.6.1.4.1.2021.4.6.0"
+ #  </Data>
+ #  <Table "ifTable">
+ #    IndexOID "IF-MIB::ifIndex"
+ #    SizeOID "IF-MIB::ifNumber"
+ #    <Data "ifDescr">
+ #      Instance true
+ #      Plugin "interface"
+ #      OIDs "IF-MIB::ifDescr"
+ #    </Data>
+ #    <Data "ifOctets">
+ #      Plugin "interface"
+ #      Type "if_octets"
+ #      TypeInstance ""
+ #      OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+ #    </Data>
+ #  </Table>
+ #</Plugin>
  #<Plugin statsd>
  #  Host "::"
  #  Port "8125"
  #             SSLVersion "TLSv1"
  #             Format "Command"
  #             Attribute "key" "value"     # only available for KAIROSDB format
+ #             TTL 0   # data ttl, only available for KAIROSDB format
  #             Metrics true
  #             Notifications false
  #             StoreRates false
diff --combined src/collectd.conf.pod
@@@ -338,7 -338,7 +338,7 @@@ is enabled by default
  =item B<PostCacheChain> I<ChainName>
  
  Configure the name of the "pre-cache chain" and the "post-cache chain". Please
- see L<FILTER CONFIGURATION> below on information on chains and how these
+ see L</"FILTER CONFIGURATION"> below on information on chains and how these
  setting change the daemon's behavior.
  
  =back
@@@ -1372,6 -1372,8 +1372,8 @@@ Select I<cgroup> based on the name. Whe
  collected or if they are ignored is controlled by the B<IgnoreSelected> option;
  see below.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> B<true>|B<false>
  
  Invert the selection: If set to true, all cgroups I<except> the ones that
@@@ -2268,14 -2270,20 +2270,20 @@@ values. Defaults to the global hostnam
  
  Select partitions based on the devicename.
  
+ See F</"IGNORELISTS"> for details.
  =item B<MountPoint> I<Directory>
  
  Select partitions based on the mountpoint.
  
+ See F</"IGNORELISTS"> for details.
  =item B<FSType> I<FSType>
  
  Select partitions based on the filesystem type.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> B<true>|B<false>
  
  Invert the selection: If set to true, all partitions B<except> the ones that
@@@ -2337,6 -2345,8 +2345,8 @@@ is interpreted as a regular expression
    Disk "sdd"
    Disk "/hda[34]/"
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> B<true>|B<false>
  
  Sets whether selected disks, i.E<nbsp>e. the ones matches by any of the B<Disk>
@@@ -2398,7 -2408,6 +2408,6 @@@ B<Synopsis:
     <EAL>
       Coremask "0x1"
       MemoryChannels "4"
-      ProcessType "secondary"
       FilePrefix "rte"
     </EAL>
     <Event "link_status">
@@@ -2421,7 -2430,7 +2430,7 @@@ B<Options:
  
  =head3 The EAL block
  
- =over 5
+ =over 4
  
  =item B<Coremask> I<Mask>
  
  
  Number of memory channels per processor socket.
  
- =item B<ProcessType> I<type>
- The type of DPDK process instance.
  =item B<FilePrefix> I<File>
  
  The prefix text used for hugepage filenames. The filename will be set to
@@@ -2447,7 -2452,7 +2452,7 @@@ single argument which specifies the nam
  
  =head4 Link Status event
  
- =over 5
+ =over 4
  
  =item B<SendEventOnUpdate> I<true|false>
  
@@@ -2480,7 -2485,7 +2485,7 @@@ value is false
  
  =head4 Keep Alive event
  
- =over 5
+ =over 4
  
  =item B<SendEventOnUpdate> I<true|false>
  
@@@ -2516,7 -2521,6 +2521,6 @@@ B<Synopsis:
     <EAL>
       Coremask "0x4"
       MemoryChannels "4"
-      ProcessType "secondary"
       FilePrefix "rte"
       SocketMemory "1024"
     </EAL>
@@@ -2530,7 -2534,7 +2534,7 @@@ B<Options:
  
  =head3 The EAL block
  
- =over 5
+ =over 4
  
  =item B<Coremask> I<Mask>
  
@@@ -2541,10 -2545,6 +2545,6 @@@ core numbering can change between platf
  
  A string containing a number of memory channels per processor socket.
  
- =item B<ProcessType> I<type>
- A string containing the type of DPDK process instance.
  =item B<FilePrefix> I<File>
  
  The prefix text used for hugepage filenames. The filename will be set to
@@@ -2560,6 -2560,7 +2560,7 @@@ sockets in MB. This is an optional valu
  =over 3
  
  =item B<SharedMemObj> I<Mask>
  A string containing the name of the shared memory object that should be used to
  share stats from the DPDK secondary process to the collectd dpdkstat plugin.
  Defaults to dpdk_collectd_stats if no other value is configured.
@@@ -3139,9 -3140,11 +3140,11 @@@ than 1 sec
  Select this interface. By default these interfaces will then be collected. For
  a more detailed description see B<IgnoreSelected> below.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> I<true>|I<false>
  
- If no configuration if given, the B<interface>-plugin will collect data from
+ If no configuration is given, the B<interface>-plugin will collect data from
  all interfaces. This may not be practical, especially for loopback- and
  similar interfaces. Thus, you can use the B<Interface>-option to pick the
  interfaces you're interested in. Sometimes, however, it's easier/preferred
@@@ -3200,6 -3203,8 +3203,8 @@@ This option is only available on Solari
  
  Selects sensors to collect or to ignore, depending on B<IgnoreSelected>.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> I<true>|I<false>
  
  If no configuration if given, the B<ipmi> plugin will collect data from all
@@@ -3256,6 -3261,8 +3261,8 @@@ comment or the number
  Select this irq. By default these irqs will then be collected. For a more
  detailed description see B<IgnoreSelected> below.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> I<true>|I<false>
  
  If no configuration if given, the B<irq>-plugin will collect data from all
@@@ -3471,36 -3478,17 +3478,36 @@@ By default the plugin connects to B<"/v
  mcelog server is running. When the server is running, the plugin will tail the
  specified logfile to retrieve machine check exception information and send a
  notification with the details from the logfile. The plugin will use the mcelog
 -client protocol to retrieve memory related machine check exceptions.
 +client protocol to retrieve memory related machine check exceptions. Note that
 +for memory exceptions, notifications are only sent when there is a change in
 +the number of corrected/uncorrected memory errors.
  
 -=over 4
 +=head3 The Memory block
 +
 +Note: these options cannot be used in conjunction with the logfile options, they are mutually
 +exclusive.
 +
 +=over 3
  
  =item B<McelogClientSocket> I<Path>
  Connect to the mcelog client socket using the UNIX domain socket at I<Path>.
  Defaults to B<"/var/run/mcelog-client">.
  
 +=item B<PersistentNotification> B<true>|B<false>
 +Override default configuration to only send notifications when sent when there
 +is a change in the number of corrected/uncorrected memory errors. When set to
 +true notifications will be sent for every read cycle. Default is false. Does
 +not affect the stats being dispatched.
 +
 +=back
 +
 +=over 4
 +
  =item B<McelogLogfile> I<Path>
  
 -The mcelog file to parse. Defaults to B<"/var/log/mcelog">.
 +The mcelog file to parse. Defaults to B<"/var/log/mcelog">. Note: this option
 +cannot be used in conjunction with the memory block options, they are mutually
 +exclusive.
  
  =back
  
@@@ -3520,6 -3508,8 +3527,8 @@@ Select md devices based on device name
  the device, i.e. the name of the block device without the leading C</dev/>.
  See B<IgnoreSelected> for more details.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> B<true>|B<false>
  
  Invert device selection: If set to B<true>, all md devices B<except> those
@@@ -4683,6 -4673,8 +4692,8 @@@ regular and exact matching are case sen
  If no volume was specified at all for either of the three options, that data
  will be collected for all available volumes.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelectedIO> B<true>|B<false>
  
  =item B<IgnoreSelectedOps> B<true>|B<false>
@@@ -4868,6 -4860,8 +4879,8 @@@ Here are some examples to help you unde
      Filter "ppp0" "u32-1:0"
    </Plugin>
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected>
  
  The behavior is the same as with all other similar plugins: If nothing is
@@@ -5419,6 -5413,8 +5432,8 @@@ C</10.F10FCA000800/temperature>). B<Ign
  As there can be multiple devices on the bus you can list multiple sensor (use
  multiple B<Sensor> elements).
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> I<true>|I<false>
  
  If no configuration is given, the B<onewire> plugin will collect data from all
@@@ -5860,10 -5856,10 +5875,10 @@@ multiple hosts
  =item B<Interval> I<Seconds>
  
  Sets the interval in which to send ICMP echo packets to the configured hosts.
- This is B<not> the interval in which statistics are queries from the plugin but
the interval in which the hosts are "pinged". Therefore, the setting here
- should be smaller than or equal to the global B<Interval> setting. Fractional
times, such as "1.24" are allowed.
+ This is B<not> the interval in which metrics are read from the plugin but the
interval in which the hosts are "pinged". Therefore, the setting here should be
+ smaller than or equal to the global B<Interval> setting. Fractional times, such
+ as "1.24" are allowed.
  
  Default: B<1.0>
  
@@@ -6601,6 -6597,8 +6616,8 @@@ Whether only matched values are selecte
  depends on the B<IgnoreSelected>. By default, only matched values are selected.
  If no value is configured at all, all values will be selected.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> B<true>|B<false>
  
  If set to B<true>, inverts the selection made by B<Value>, i.E<nbsp>e. all
@@@ -7029,6 -7027,8 +7046,8 @@@ on the B<IgnoreSelected> below. For exa
  I<it8712-isa-0290/voltage-in1>" will cause collectd to gather data for the
  voltage sensor I<in1> of the I<it8712> on the isa bus at the address 0290.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> I<true>|I<false>
  
  If no configuration if given, the B<sensors>-plugin will collect data from all
@@@ -7138,6 -7138,8 +7157,8 @@@ is interpreted as a regular expression
    Disk "sdd"
    Disk "/hda[34]/"
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> B<true>|B<false>
  
  Sets whether selected disks, i.E<nbsp>e. the ones matches by any of the B<Disk>
@@@ -7171,6 -7173,122 +7192,122 @@@ Since the configuration of the C<snmp p
  other plugins, its documentation has been moved to an own manpage,
  L<collectd-snmp(5)>. Please see there for details.
  
+ =head2 Plugin C<snmp_agent>
+ The I<snmp_agent> plugin is an AgentX subagent that receives and handles queries
+ from SNMP master agent and returns the data collected by read plugins.
+ The I<snmp_agent> plugin handles requests only for OIDs specified in
+ configuration file. To handle SNMP queries the plugin gets data from collectd
+ and translates requested values from collectd's internal format to SNMP format.
+ This plugin is a generic plugin and cannot work without configuration.
+ For more details on AgentX subagent see
+ <http://www.net-snmp.org/tutorial/tutorial-5/toolkit/demon/>
+ B<Synopsis:>
+   <Plugin snmp_agent>
+     <Data "memAvailReal">
+       Plugin "memory"
+       #PluginInstance "some"
+       Type "memory"
+       TypeInstance "free"
+       OIDs "1.3.6.1.4.1.2021.4.6.0"
+     </Data>
+     <Table "ifTable">
+       IndexOID "IF-MIB::ifIndex"
+       SizeOID "IF-MIB::ifNumber"
+       <Data "ifDescr">
+         Instance true
+         Plugin "interface"
+         OIDs "IF-MIB::ifDescr"
+       </Data>
+       <Data "ifOctets">
+         Plugin "interface"
+         Type "if_octets"
+         TypeInstance ""
+         OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+       </Data>
+     </Table>
+   </Plugin>
+ There are two types of blocks that can be contained in the
+ C<E<lt>PluginE<nbsp> snmp_agentE<gt>> block: B<Data> and B<Table>:
+ =head3 The B<Data> block
+ The B<Data> block defines a list OIDs that are to be handled. This block can
+ define scalar or table OIDs. If B<Data> block is defined inside of B<Table>
+ block it reperesents table OIDs.
+ The following options can be set:
+ =over 4
+ =item B<Instance> I<true|false>
+ When B<Instance> is set to B<true>, the value for requested OID is copied from
+ plugin instance field of corresponding collectd value. If B<Data> block defines
+ scalar data type B<Instance> has no effect and can be omitted.
+ =item B<Plugin> I<String>
+ Read plugin name whose collected data will be mapped to specified OIDs.
+ =item B<PluginInstance> I<String>
+ Read plugin instance whose collected data will be mapped to specified OIDs.
+ The field is optional and by default there is no plugin instance check.
+ Allowed only if B<Data> block defines scalar data type.
+ =item B<Type> I<String>
+ Collectd's type that is to be used for specified OID, e.E<nbsp>g. "if_octets"
+ for example. The types are read from the B<TypesDB> (see L<collectd.conf(5)>).
+ =item B<TypeInstance> I<String>
+ Collectd's type-instance that is to be used for specified OID.
+ =item B<OIDs> I<OID> [I<OID> ...]
+ Configures the OIDs to be handled by I<snmp_agent> plugin. Values for these OIDs
+ are taken from collectd data type specified by B<Plugin>, B<PluginInstance>,
+ B<Type>, B<TypeInstance> fields of this B<Data> block. Number of the OIDs
+ configured should correspond to number of values in specified B<Type>.
+ For example two OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets" can be mapped to
+ "rx" and "tx" values of "if_octets" type.
+ =item B<Scale> I<Value>
+ The values taken from collectd are multiplied by I<Value>. The field is optional
+ and the default is B<1.0>.
+ =item B<Shift> I<Value>
+ I<Value> is added to values from collectd after they have been multiplied by
+ B<Scale> value. The field is optional and the default value is B<0.0>.
+ =back
+ =head3 The B<Table> block
+ The B<Table> block defines a collection of B<Data> blocks that belong to one
+ snmp table. In addition to multiple B<Data> blocks the following options can be
+ set:
+ =over 4
+ =item B<IndexOID> I<OID>
+ OID that is handled by the plugin and is mapped to numerical index value that is
+ generated by the plugin for each table record.
+ =item B<SizeOID> I<OID>
+ OID that is handled by the plugin. Returned value is the number of records in
+ the table. The field is optional.
+ =back
  =head2 Plugin C<statsd>
  
  The I<statsd plugin> listens to a UDP socket, reads "events" in the statsd
@@@ -7825,6 -7943,8 +7962,8 @@@ Selects the name of the thermal device 
  depending on the value of the B<IgnoreSelected> option. This option may be
  used multiple times to specify a list of devices.
  
+ See F</"IGNORELISTS"> for details.
  =item B<IgnoreSelected> I<true>|I<false>
  
  Invert the selection: If set to true, all devices B<except> the ones that
@@@ -8790,13 -8910,21 +8929,21 @@@ Defaults to B<Command>
  
  =item B<Attribute> I<String> I<String>
  
- Only available for KAIROSDB output format.
+ Only available for the KAIROSDB output format.
  
  Consider the two given strings to be the key and value of an additional tag for
  each metric being sent out.
  
  You can add multiple B<Attribute>.
  
+ =item B<TTL> I<Int>
+ Only available for the KAIROSDB output format.
+ Sets the Cassandra ttl for the data points.
+ Please refer to L<http://kairosdb.github.io/docs/build/html/restapi/AddDataPoints.html?highlight=ttl>
  =item B<Metrics> B<true>|B<false>
  
  Controls whether I<metrics> are POSTed to this location. Defaults to B<true>.
@@@ -10284,6 -10412,48 +10431,48 @@@ be an FQDN
     Target "write"
   </Chain>
  
+ =head1 IGNORELISTS
+ B<Ignorelists> are a generic framework to either ignore some metrics or report
+ specific metircs only. Plugins usually provide one or more options to specify
+ the items (mounts points, devices, ...) and the boolean option
+ C<IgnoreSelected>.
+ =over 4
+ =item B<Select> I<String>
+ Selects the item I<String>. This option often has a plugin specific name, e.g.
+ B<Sensor> in the C<sensors> plugin. It is also plugin specific what this string
+ is compared to. For example, the C<df> plugin's B<MountPoint> compares it to a
+ mount point and the C<sensors> plugin's B<Sensor> compares it to a sensor name.
+ By default, this option is doing a case-sensitive full-string match. The
+ following config will match C<foo>, but not C<Foo>:
+   Select "foo"
+ If I<String> starts and ends with C</> (a slash), the string is compiled as a
+ I<regular expression>. For example, so match all item starting with C<foo>, use
+ could use the following syntax:
+   Select "/^foo/"
+ The regular expression is I<not> anchored, i.e. the following config will match
+ C<foobar>, C<barfoo> and C<AfooZ>:
+   Select "/foo/"
+ The B<Select> option may be repeated to select multiple items.
+ =item B<IgnoreSelected> B<true>|B<false>
+ If set to B<true>, matching metrics are I<ignored> and all other metrics are
+ collected. If set to B<false>, matching metrics are I<collected> and all other
+ metrics are ignored.
+ =back
  =head1 SEE ALSO
  
  L<collectd(1)>,
diff --combined src/mcelog.c
@@@ -2,7 -2,7 +2,7 @@@
   * collectd - src/mcelog.c
   * MIT License
   *
 - * Copyright(c) 2016 Intel Corporation. All rights reserved.
 + * Copyright(c) 2016-2017 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"),
   *   Krzysztof Matczak <krzysztofx.matczak@intel.com>
   */
  
 -#include "common.h"
  #include "collectd.h"
  
 +#include "common.h"
 +#include "utils_llist.h"
 +
  #include <poll.h>
  #include <sys/socket.h>
  #include <sys/un.h>
  #define MCELOG_DIMM_NAME "DMI_NAME"
  #define MCELOG_CORRECTED_ERR "corrected memory errors"
  #define MCELOG_UNCORRECTED_ERR "uncorrected memory errors"
 +#define MCELOG_CORRECTED_ERR_TIMED "corrected memory timed errors"
 +#define MCELOG_UNCORRECTED_ERR_TIMED "uncorrected memory timed errors"
 +#define MCELOG_CORRECTED_ERR_TYPE_INS "corrected_memory_errors"
 +#define MCELOG_UNCORRECTED_ERR_TYPE_INS "uncorrected_memory_errors"
  
  typedef struct mcelog_config_s {
    char logfile[PATH_MAX]; /* mcelog logfile */
    pthread_t tid;          /* poll thread id */
 +  llist_t *dimms_list;    /* DIMMs list */
 +  /* lock for dimms cache */
 +  _Bool persist;
 +  pthread_mutex_t dimms_lock;
  } mcelog_config_t;
  
  typedef struct socket_adapter_s socket_adapter_t;
@@@ -90,9 -80,7 +90,9 @@@ static int socket_write(socket_adapter_
  static int socket_reinit(socket_adapter_t *self);
  static int socket_receive(socket_adapter_t *self, FILE **p_file);
  
 -static mcelog_config_t g_mcelog_config = {.logfile = "/var/log/mcelog"};
 +static mcelog_config_t g_mcelog_config = {
 +    .logfile = "/var/log/mcelog", .persist = 0,
 +};
  
  static socket_adapter_t socket_adapter = {
      .sock_fd = -1,
  };
  
  static _Bool mcelog_thread_running;
 +static _Bool mcelog_apply_defaults;
 +
 +static void mcelog_free_dimms_list_records(llist_t *dimms_list) {
 +
 +  for (llentry_t *e = llist_head(dimms_list); e != NULL; e = e->next) {
 +    sfree(e->key);
 +    sfree(e->value);
 +  }
 +}
 +
 +/* Create or get dimm by dimm name/location */
 +static llentry_t *mcelog_dimm(const mcelog_memory_rec_t *rec,
 +                              llist_t *dimms_list) {
 +
 +  char dimm_name[DATA_MAX_NAME_LEN];
 +
 +  if (strlen(rec->dimm_name) > 0) {
 +    ssnprintf(dimm_name, sizeof(dimm_name), "%s_%s", rec->location,
 +              rec->dimm_name);
 +  } else
 +    sstrncpy(dimm_name, rec->location, sizeof(dimm_name));
 +
 +  llentry_t *dimm_le = llist_search(g_mcelog_config.dimms_list, dimm_name);
 +
 +  if (dimm_le == NULL) {
 +    mcelog_memory_rec_t *dimm_mr = calloc(1, sizeof(*dimm_mr));
 +    if (dimm_mr == NULL) {
 +      ERROR(MCELOG_PLUGIN ": Error allocating dimm memory item");
 +      return NULL;
 +    }
 +    char *p_name = strdup(dimm_name);
 +    if (p_name == NULL) {
 +      ERROR(MCELOG_PLUGIN ": strdup: error");
 +      free(dimm_mr);
 +      return NULL;
 +    }
 +
 +    /* add new dimm */
 +    dimm_le = llentry_create(p_name, dimm_mr);
 +    if (dimm_le == NULL) {
 +      ERROR(MCELOG_PLUGIN ": llentry_create(): error");
 +      free(dimm_mr);
 +      free(p_name);
 +      return NULL;
 +    }
 +    pthread_mutex_lock(&g_mcelog_config.dimms_lock);
 +    llist_append(g_mcelog_config.dimms_list, dimm_le);
 +    pthread_mutex_unlock(&g_mcelog_config.dimms_lock);
 +  }
 +
 +  return dimm_le;
 +}
 +
 +static void mcelog_update_dimm_stats(llentry_t *dimm,
 +                                     const mcelog_memory_rec_t *rec) {
 +  pthread_mutex_lock(&g_mcelog_config.dimms_lock);
 +  memcpy(dimm->value, rec, sizeof(mcelog_memory_rec_t));
 +  pthread_mutex_unlock(&g_mcelog_config.dimms_lock);
 +}
  
  static int mcelog_config(oconfig_item_t *ci) {
 +  int use_logfile = 0, use_memory = 0;
    for (int i = 0; i < ci->children_num; i++) {
      oconfig_item_t *child = ci->children + i;
 -    if (strcasecmp("McelogClientSocket", child->key) == 0) {
 -      if (cf_util_get_string_buffer(child, socket_adapter.unix_sock.sun_path,
 -                                    sizeof(socket_adapter.unix_sock.sun_path)) <
 -          0) {
 -        ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
 +    if (strcasecmp("McelogLogfile", child->key) == 0) {
 +      use_logfile = 1;
 +      if (use_memory) {
 +        ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\", Memory "
 +                            "option is already configured.",
                child->key);
-         return (-1);
+         return -1;
        }
 -    } else if (strcasecmp("McelogLogfile", child->key) == 0) {
        if (cf_util_get_string_buffer(child, g_mcelog_config.logfile,
                                      sizeof(g_mcelog_config.logfile)) < 0) {
          ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
                child->key);
-         return (-1);
+         return -1;
        }
 +      memset(socket_adapter.unix_sock.sun_path, 0,
 +             sizeof(socket_adapter.unix_sock.sun_path));
 +    } else if (strcasecmp("Memory", child->key) == 0) {
 +      if (use_logfile) {
 +        ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\", Logfile "
 +                            "option is already configured.",
 +              child->key);
 +        return (-1);
 +      }
 +      use_memory = 1;
 +      oconfig_item_t *mem_child = child->children;
 +      for (int j = 0; j < child->children_num; j++) {
 +        mem_child += j;
 +        if (strcasecmp("McelogClientSocket", mem_child->key) == 0) {
 +          if (cf_util_get_string_buffer(
 +                  mem_child, socket_adapter.unix_sock.sun_path,
 +                  sizeof(socket_adapter.unix_sock.sun_path)) < 0) {
 +            ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
 +                  mem_child->key);
 +            return (-1);
 +          }
 +        } else if (strcasecmp("PersistentNotification", mem_child->key) == 0) {
 +          if (cf_util_get_boolean(mem_child, &g_mcelog_config.persist) < 0) {
 +            ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
 +                  mem_child->key);
 +            return (-1);
 +          }
 +        } else {
 +          ERROR(MCELOG_PLUGIN ": Invalid Memory configuration option: \"%s\".",
 +                mem_child->key);
 +          return (-1);
 +        }
 +      }
 +      memset(g_mcelog_config.logfile, 0, sizeof(g_mcelog_config.logfile));
      } else {
        ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
              child->key);
-       return (-1);
+       return -1;
      }
    }
 -  return 0;
 +
 +  if (!use_logfile && !use_memory)
 +    mcelog_apply_defaults = 1;
 +
 +  return (0);
  }
  
  static int socket_close(socket_adapter_t *self) {
      }
    }
    pthread_rwlock_unlock(&self->lock);
-   return (ret);
+   return ret;
  }
  
  static int socket_write(socket_adapter_t *self, const char *msg,
    if (swrite(self->sock_fd, msg, len) < 0)
      ret = -1;
    pthread_rwlock_unlock(&self->lock);
-   return (ret);
+   return ret;
  }
  
  static void mcelog_dispatch_notification(notification_t *n) {
@@@ -290,7 -181,7 +290,7 @@@ static int socket_reinit(socket_adapter
      ERROR(MCELOG_PLUGIN ": Could not create a socket. %s",
            sstrerror(errno, errbuff, sizeof(errbuff)));
      pthread_rwlock_unlock(&self->lock);
-     return (ret);
+     return ret;
    }
  
    /* Set socket timeout option */
                            .type_instance = "mcelog_status"});
    }
    pthread_rwlock_unlock(&self->lock);
-   return (ret);
+   return ret;
  }
  
 -static int mcelog_prepare_notification(notification_t *n,
 -                                       const mcelog_memory_rec_t *mr) {
 -  if (n == NULL || mr == NULL)
 -    return -1;
 +static int mcelog_dispatch_mem_notifications(const mcelog_memory_rec_t *mr) {
 +  notification_t n = {.severity = NOTIF_WARNING,
 +                      .time = cdtime(),
 +                      .plugin = MCELOG_PLUGIN,
 +                      .type = "errors"};
  
 -  if ((mr->location[0] != '\0') &&
 -      (plugin_notification_meta_add_string(n, MCELOG_SOCKET_STR, mr->location) <
 -       0)) {
 -    ERROR(MCELOG_PLUGIN ": add memory location meta data failed");
 -    return -1;
 -  }
 -  if ((mr->dimm_name[0] != '\0') &&
 -      (plugin_notification_meta_add_string(n, MCELOG_DIMM_NAME, mr->dimm_name) <
 -       0)) {
 -    ERROR(MCELOG_PLUGIN ": add DIMM name meta data failed");
 -    plugin_notification_meta_free(n->meta);
 -    return -1;
 -  }
 -  if (plugin_notification_meta_add_signed_int(n, MCELOG_CORRECTED_ERR,
 -                                              mr->corrected_err_total) < 0) {
 -    ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed");
 -    plugin_notification_meta_free(n->meta);
 -    return -1;
 -  }
 -  if (plugin_notification_meta_add_signed_int(
 -          n, "corrected memory timed errors", mr->corrected_err_timed) < 0) {
 -    ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed");
 -    plugin_notification_meta_free(n->meta);
 -    return -1;
 -  }
 -  if ((mr->corrected_err_timed_period[0] != '\0') &&
 -      (plugin_notification_meta_add_string(n, "corrected errors time period",
 -                                           mr->corrected_err_timed_period) <
 -       0)) {
 -    ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed");
 -    plugin_notification_meta_free(n->meta);
 -    return -1;
 +  int dispatch_corrected_notifs = 0, dispatch_uncorrected_notifs = 0;
 +
 +  if (mr == NULL)
 +    return (-1);
 +
 +  llentry_t *dimm = mcelog_dimm(mr, g_mcelog_config.dimms_list);
 +  if (dimm == NULL) {
 +    ERROR(MCELOG_PLUGIN
 +          ": Error adding/getting dimm memory item to/from cache");
 +    return (-1);
    }
 -  if (plugin_notification_meta_add_signed_int(n, MCELOG_UNCORRECTED_ERR,
 -                                              mr->uncorrected_err_total) < 0) {
 -    ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed");
 -    plugin_notification_meta_free(n->meta);
 -    return -1;
 +  mcelog_memory_rec_t *mr_old = dimm->value;
 +  if (!g_mcelog_config.persist) {
 +
 +    if (mr_old->corrected_err_total != mr->corrected_err_total ||
 +        mr_old->corrected_err_timed != mr->corrected_err_timed)
 +      dispatch_corrected_notifs = 1;
 +
 +    if (mr_old->uncorrected_err_total != mr->uncorrected_err_total ||
 +        mr_old->uncorrected_err_timed != mr->uncorrected_err_timed)
 +      dispatch_uncorrected_notifs = 1;
 +
 +    if (!dispatch_corrected_notifs && !dispatch_uncorrected_notifs) {
 +      DEBUG("%s: No new notifications to dispatch", MCELOG_PLUGIN);
 +      return (0);
 +    }
 +  } else {
 +    dispatch_corrected_notifs = 1;
 +    dispatch_uncorrected_notifs = 1;
    }
 -  if (plugin_notification_meta_add_signed_int(n,
 -                                              "uncorrected memory timed errors",
 -                                              mr->uncorrected_err_timed) < 0) {
 -    ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed");
 -    plugin_notification_meta_free(n->meta);
 -    return -1;
 +
 +  sstrncpy(n.host, hostname_g, sizeof(n.host));
 +
 +  if (mr->dimm_name[0] != '\0')
 +    ssnprintf(n.plugin_instance, sizeof(n.plugin_instance), "%s_%s",
 +              mr->location, mr->dimm_name);
 +  else
 +    sstrncpy(n.plugin_instance, mr->location, sizeof(n.plugin_instance));
 +
 +  if (dispatch_corrected_notifs &&
 +      (mr->corrected_err_total > 0 || mr->corrected_err_timed > 0)) {
 +    /* Corrected Error Notifications */
 +    plugin_notification_meta_add_signed_int(&n, MCELOG_CORRECTED_ERR,
 +                                            mr->corrected_err_total);
 +    plugin_notification_meta_add_signed_int(&n, MCELOG_CORRECTED_ERR_TIMED,
 +                                            mr->corrected_err_timed);
 +    ssnprintf(n.message, sizeof(n.message), MCELOG_CORRECTED_ERR);
 +    sstrncpy(n.type_instance, MCELOG_CORRECTED_ERR_TYPE_INS,
 +             sizeof(n.type_instance));
 +    plugin_dispatch_notification(&n);
 +    if (n.meta)
 +      plugin_notification_meta_free(n.meta);
 +    n.meta = NULL;
    }
 -  if ((mr->uncorrected_err_timed_period[0] != '\0') &&
 -      (plugin_notification_meta_add_string(n, "uncorrected errors time period",
 -                                           mr->uncorrected_err_timed_period) <
 -       0)) {
 -    ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed");
 -    plugin_notification_meta_free(n->meta);
 -    return -1;
 +
 +  if (dispatch_uncorrected_notifs &&
 +      (mr->uncorrected_err_total > 0 || mr->uncorrected_err_timed > 0)) {
 +    /* Uncorrected Error Notifications */
 +    plugin_notification_meta_add_signed_int(&n, MCELOG_UNCORRECTED_ERR,
 +                                            mr->uncorrected_err_total);
 +    plugin_notification_meta_add_signed_int(&n, MCELOG_UNCORRECTED_ERR_TIMED,
 +                                            mr->uncorrected_err_timed);
 +    ssnprintf(n.message, sizeof(n.message), MCELOG_UNCORRECTED_ERR);
 +    sstrncpy(n.type_instance, MCELOG_UNCORRECTED_ERR_TYPE_INS,
 +             sizeof(n.type_instance));
 +    n.severity = NOTIF_FAILURE;
 +    plugin_dispatch_notification(&n);
 +    if (n.meta)
 +      plugin_notification_meta_free(n.meta);
 +    n.meta = NULL;
    }
  
-   return (0);
+   return 0;
  }
  
  static int mcelog_submit(const mcelog_memory_rec_t *mr) {
  
    if (!mr) {
      ERROR(MCELOG_PLUGIN ": %s: NULL pointer", __FUNCTION__);
-     return (-1);
+     return -1;
    }
  
 +  llentry_t *dimm = mcelog_dimm(mr, g_mcelog_config.dimms_list);
 +  if (dimm == NULL) {
 +    ERROR(MCELOG_PLUGIN
 +          ": Error adding/getting dimm memory item to/from cache");
 +    return (-1);
 +  }
 +
    value_list_t vl = {
        .values_len = 1,
        .values = &(value_t){.derive = (derive_t)mr->corrected_err_total},
        .time = cdtime(),
        .plugin = MCELOG_PLUGIN,
        .type = "errors",
 -      .type_instance = "corrected_memory_errors"};
 +      .type_instance = MCELOG_CORRECTED_ERR_TYPE_INS};
 +
 +  mcelog_update_dimm_stats(dimm, mr);
  
    if (mr->dimm_name[0] != '\0')
      ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s_%s",
    vl.values = &(value_t){.derive = (derive_t)mr->corrected_err_timed};
    plugin_dispatch_values(&vl);
  
 -  sstrncpy(vl.type_instance, "uncorrected_memory_errors",
 +  sstrncpy(vl.type_instance, MCELOG_UNCORRECTED_ERR_TYPE_INS,
             sizeof(vl.type_instance));
    vl.values = &(value_t){.derive = (derive_t)mr->uncorrected_err_total};
    plugin_dispatch_values(&vl);
    vl.values = &(value_t){.derive = (derive_t)mr->uncorrected_err_timed};
    plugin_dispatch_values(&vl);
  
-   return (0);
+   return 0;
  }
  
  static int parse_memory_info(FILE *p_file, mcelog_memory_rec_t *memory_record) {
      /* Got empty line or "done" */
      if ((!strncmp("\n", buf, strlen(buf))) ||
          (!strncmp(buf, "done\n", strlen(buf))))
-       return (1);
+       return 1;
      if (strlen(buf) < 5)
        continue;
      if (!strncmp(buf, MCELOG_SOCKET_STR, strlen(MCELOG_SOCKET_STR))) {
      memset(buf, 0, sizeof(buf));
    }
    /* parsing definitely finished */
-   return (0);
+   return 0;
  }
  
  static void poll_worker_cleanup(void *arg) {
@@@ -540,7 -404,7 +540,7 @@@ static int socket_receive(socket_adapte
              sstrerror(errno, errbuf, sizeof(errbuf)));
      }
      pthread_rwlock_unlock(&self->lock);
-     return (res);
+     return res;
    }
  
    if (poll_fd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
                              .type_instance = "mcelog_status"});
      }
      pthread_rwlock_unlock(&self->lock);
-     return (-1);
+     return -1;
    }
  
    if (!(poll_fd.revents & (POLLIN | POLLPRI))) {
      INFO(MCELOG_PLUGIN ": No data to read");
      pthread_rwlock_unlock(&self->lock);
-     return (0);
+     return 0;
    }
  
    if ((*pp_file = fdopen(dup(self->sock_fd), "r")) == NULL)
      res = -1;
  
    pthread_rwlock_unlock(&self->lock);
-   return (res);
+   return res;
  }
  
  static void *poll_worker(__attribute__((unused)) void *arg) {
          continue;
        }
  
 -      notification_t n = {.severity = NOTIF_OKAY,
 -                          .time = cdtime(),
 -                          .message = "Got memory errors info.",
 -                          .plugin = MCELOG_PLUGIN,
 -                          .type_instance = "memory_erros"};
 -
 -      if (mcelog_prepare_notification(&n, &memory_record) == 0)
 -        mcelog_dispatch_notification(&n);
 +      if (mcelog_dispatch_mem_notifications(&memory_record) != 0)
 +        ERROR(MCELOG_PLUGIN ": Failed to submit memory errors notification");
        if (mcelog_submit(&memory_record) != 0)
          ERROR(MCELOG_PLUGIN ": Failed to submit memory errors");
        memset(&memory_record, 0, sizeof(memory_record));
  
    mcelog_thread_running = 0;
    pthread_cleanup_pop(1);
-   return (NULL);
+   return NULL;
  }
  
  static int mcelog_init(void) {
 +  if (mcelog_apply_defaults) {
 +    INFO(MCELOG_PLUGIN
 +         ": No configuration selected defaulting to memory errors.");
 +    memset(g_mcelog_config.logfile, 0, sizeof(g_mcelog_config.logfile));
 +  }
 +  g_mcelog_config.dimms_list = llist_create();
 +  int err = pthread_mutex_init(&g_mcelog_config.dimms_lock, NULL);
 +  if (err < 0) {
 +    ERROR(MCELOG_PLUGIN ": plugin: failed to initialize cache lock");
 +    return (-1);
 +  }
 +
    if (socket_adapter.reinit(&socket_adapter) != 0) {
      ERROR(MCELOG_PLUGIN ": Cannot connect to client socket");
-     return (-1);
+     return -1;
    }
  
 -  if (plugin_thread_create(&g_mcelog_config.tid, NULL, poll_worker, NULL,
 -                           NULL) != 0) {
 -    ERROR(MCELOG_PLUGIN ": Error creating poll thread.");
 -    return -1;
 +  if (strlen(socket_adapter.unix_sock.sun_path)) {
 +    if (plugin_thread_create(&g_mcelog_config.tid, NULL, poll_worker, NULL,
 +                             NULL) != 0) {
 +      ERROR(MCELOG_PLUGIN ": Error creating poll thread.");
 +      return (-1);
 +    }
    }
-   return (0);
+   return 0;
  }
  
  static int get_memory_machine_checks(void) {
      ERROR(MCELOG_PLUGIN ": SENT DUMP REQUEST FAILED");
    else
      DEBUG(MCELOG_PLUGIN ": SENT DUMP REQUEST OK");
-   return (ret);
+   return ret;
  }
  
  static int mcelog_read(__attribute__((unused)) user_data_t *ud) {
    if (get_memory_machine_checks() != 0)
      ERROR(MCELOG_PLUGIN ": MACHINE CHECK INFO NOT AVAILABLE");
  
-   return (0);
+   return 0;
  }
  
  static int mcelog_shutdown(void) {
        ret = -1;
      }
    }
 -
 +  pthread_mutex_lock(&g_mcelog_config.dimms_lock);
 +  mcelog_free_dimms_list_records(g_mcelog_config.dimms_list);
 +  llist_destroy(g_mcelog_config.dimms_list);
 +  g_mcelog_config.dimms_list = NULL;
 +  pthread_mutex_unlock(&g_mcelog_config.dimms_lock);
 +  pthread_mutex_destroy(&g_mcelog_config.dimms_lock);
    ret = socket_adapter.close(&socket_adapter) || ret;
    pthread_rwlock_destroy(&(socket_adapter.lock));
-   return (-ret);
+   return -ret;
  }
  
  void module_register(void) {