Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/zabbix/zabbix.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorArtjoms Rimdjonoks <artjoms.rimdjonoks@zabbix.com>2022-04-06 16:28:07 +0300
committerArtjoms Rimdjonoks <artjoms.rimdjonoks@zabbix.com>2022-04-06 16:28:07 +0300
commit17d06d7a89b2061af9b197dda90028a1b9a1380d (patch)
treeef55771b4ee832429fcaab41cf672f7be68103a8 /src
parent9f13ff30054f586b5f9ed6c856309be3acdc0cc9 (diff)
parentf721d53c0bfc1371fe9e45c97ae6c2c5057d579b (diff)
.......... [DEV-2133] updated to latest master
Diffstat (limited to 'src')
-rw-r--r--src/go/pkg/zbxcmd/zbxcmd_nix.go5
-rw-r--r--src/go/plugins/smart/smart.go270
-rw-r--r--src/go/plugins/smart/smart_nix.go2
-rw-r--r--src/go/plugins/smart/smart_test.go422
-rw-r--r--src/go/plugins/smart/smartfs.go125
-rw-r--r--src/libs/zbxalgo/linked_list.c31
-rw-r--r--src/libs/zbxnix/daemon.c28
-rw-r--r--src/zabbix_server/ha/ha_manager.c102
-rw-r--r--src/zabbix_server/lld/lld_item.c10
-rw-r--r--src/zabbix_server/preprocessor/preproc_manager.c104
10 files changed, 959 insertions, 140 deletions
diff --git a/src/go/pkg/zbxcmd/zbxcmd_nix.go b/src/go/pkg/zbxcmd/zbxcmd_nix.go
index addd629406e..59f83a49303 100644
--- a/src/go/pkg/zbxcmd/zbxcmd_nix.go
+++ b/src/go/pkg/zbxcmd/zbxcmd_nix.go
@@ -1,5 +1,4 @@
//go:build !windows
-// +build !windows
/*
** Zabbix
@@ -65,7 +64,9 @@ func execute(s string, timeout time.Duration, path string, strict bool) (string,
// we need to check error after t.Stop so we can inform the user if timeout was reached and Zabbix agent2 terminated the command
if strict && werr != nil && !errors.Is(werr, syscall.ECHILD) {
- return "", fmt.Errorf("Command execution failed: %s", werr)
+ log.Debugf("Command [%s] execution failed: %s\n%s", s, werr, b.String())
+
+ return "", fmt.Errorf("Command execution failed: %w", werr)
}
if MaxExecuteOutputLenB <= len(b.String()) {
diff --git a/src/go/plugins/smart/smart.go b/src/go/plugins/smart/smart.go
index f71407b6b97..36bb16d9b8a 100644
--- a/src/go/plugins/smart/smart.go
+++ b/src/go/plugins/smart/smart.go
@@ -21,12 +21,27 @@ package smart
import (
"encoding/json"
+ "fmt"
+ "strings"
"zabbix.com/pkg/conf"
"zabbix.com/pkg/plugin"
"zabbix.com/pkg/zbxerr"
)
+const (
+ twoParameters = 2
+ oneParameter = 1
+ all = 0
+
+ firstParameter = 0
+ secondParameter = 1
+
+ diskGet = "smart.disk.get"
+ diskDiscovery = "smart.disk.discovery"
+ attributeDiscovery = "smart.attribute.discovery"
+)
+
// Options -
type Options struct {
plugin.SystemOptions `conf:"optional,name=System"`
@@ -61,7 +76,7 @@ func (p *Plugin) Validate(options interface{}) error {
// Export -
func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
- if len(params) > 0 {
+ if len(params) > 0 && key != diskGet {
return nil, zbxerr.ErrorTooManyParameters
}
@@ -72,84 +87,217 @@ func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider)
var jsonArray []byte
switch key {
- case "smart.disk.discovery":
- out := []device{}
-
- r, err := p.execute(false)
+ case diskDiscovery:
+ jsonArray, err = p.diskDiscovery()
if err != nil {
- return nil, err
- }
-
- for _, dev := range r.devices {
- out = append(out, device{
- Name: cutPrefix(dev.Info.Name),
- DeviceType: getType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table),
- Model: dev.ModelName, SerialNumber: dev.SerialNumber,
- })
+ return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
}
- jsonArray, err = json.Marshal(out)
+ case diskGet:
+ jsonArray, err = p.diskGet(params)
if err != nil {
- return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
}
- case "smart.disk.get":
- r, err := p.execute(true)
+ case attributeDiscovery:
+ jsonArray, err = p.attributeDiscovery()
if err != nil {
- return nil, err
+ return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
}
- fields, err := setDiskFields(r.jsonDevices)
- if err != nil {
- return nil, err
- }
+ default:
+ return nil, zbxerr.ErrorUnsupportedMetric
+ }
- if fields == nil {
- jsonArray, err = json.Marshal([]string{})
- if err != nil {
- return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
- }
+ return string(jsonArray), nil
+}
- break
- }
+func (p *Plugin) diskDiscovery() (jsonArray []byte, err error) {
+ out := []device{}
+
+ r, err := p.execute(false)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, dev := range r.devices {
+ out = append(out, device{
+ Name: cutPrefix(dev.Info.Name),
+ DeviceType: getType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table),
+ Model: dev.ModelName,
+ SerialNumber: dev.SerialNumber,
+ Path: dev.Info.name,
+ RaidType: dev.Info.raidType,
+ Attributes: getAttributes(dev),
+ })
+ }
+
+ jsonArray, err = json.Marshal(out)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
+
+ return
+}
+
+func (p *Plugin) diskGet(params []string) ([]byte, error) {
+ switch len(params) {
+ case twoParameters:
+ return p.diskGetSingle(params[firstParameter], params[secondParameter])
+ case oneParameter:
+ return p.diskGetSingle(params[firstParameter], "")
+ case all:
+ return p.diskGetAll()
+ default:
+ return nil, zbxerr.ErrorTooManyParameters
+ }
+}
+
+func (p *Plugin) diskGetSingle(path, raidType string) ([]byte, error) {
+ executable := path
+
+ if raidType != "" {
+ executable = fmt.Sprintf("%s -d %s", executable, raidType)
+ }
+
+ device, err := p.executeSingle(executable)
+ if err != nil {
+ return nil, err
+ }
+
+ out, err := setSingleDiskFields(device)
+ if err != nil {
+ return nil, err
+ }
+
+ jsonArray, err := json.Marshal(out)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
+
+ return jsonArray, nil
+}
+
+func (p *Plugin) diskGetAll() (jsonArray []byte, err error) {
+ r, err := p.execute(true)
+ if err != nil {
+ return nil, err
+ }
- jsonArray, err = json.Marshal(fields)
+ fields, err := setDiskFields(r.jsonDevices)
+ if err != nil {
+ return nil, err
+ }
+
+ if fields == nil {
+ jsonArray, err = json.Marshal([]string{})
if err != nil {
return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
}
- case "smart.attribute.discovery":
- out := []attribute{}
+ return
+ }
- r, err := p.execute(false)
- if err != nil {
- return nil, err
- }
+ jsonArray, err = json.Marshal(fields)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
- for _, dev := range r.devices {
- t := getAttributeType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table)
- for _, attr := range dev.SmartAttributes.Table {
- out = append(
- out, attribute{
- Name: cutPrefix(dev.Info.Name),
- DeviceType: t,
- ID: attr.ID,
- Attrname: attr.Attrname,
- Thresh: attr.Thresh,
- })
- }
+ return
+}
+
+func (p *Plugin) attributeDiscovery() (jsonArray []byte, err error) {
+ out := []attribute{}
+
+ r, err := p.execute(false)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, dev := range r.devices {
+ t := getAttributeType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table)
+ for _, attr := range dev.SmartAttributes.Table {
+ out = append(
+ out, attribute{
+ Name: cutPrefix(dev.Info.Name),
+ DeviceType: t,
+ ID: attr.ID,
+ Attrname: attr.Attrname,
+ Thresh: attr.Thresh,
+ })
}
+ }
- jsonArray, err = json.Marshal(out)
- if err != nil {
- return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ jsonArray, err = json.Marshal(out)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
+
+ return
+}
+
+// setSingleDiskFields goes through provided device json data and sets required output fields.
+// It returns an error if there is an issue with unmarshal for the provided input JSON map.
+func setSingleDiskFields(dev []byte) (out map[string]interface{}, err error) {
+ attr := make(map[string]interface{})
+ if err = json.Unmarshal(dev, &attr); err != nil {
+ return out, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err)
+ }
+
+ var sd singleDevice
+ if err = json.Unmarshal(dev, &sd); err != nil {
+ return out, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err)
+ }
+
+ diskType := getType(getTypeFromJson(attr), getRateFromJson(attr), getTablesFromJson(attr))
+
+ out = map[string]interface{}{}
+ out["disk_type"] = diskType
+ out["firmware_version"] = sd.Firmware
+ out["model_name"] = sd.ModelName
+ out["serial_number"] = sd.SerialNumber
+ out["exit_status"] = sd.Smartctl.ExitStatus
+
+ var errors []string
+ for _, msg := range sd.Smartctl.Messages {
+ errors = append(errors, msg.Str)
+ }
+
+ out["error"] = strings.Join(errors, ", ")
+ out["self_test_passed"] = setSelfTest(sd)
+
+ if diskType == nvmeType {
+ out["temperature"] = sd.HealthLog.Temperature
+ out["power_on_time"] = sd.HealthLog.PowerOnTime
+ out["critical_warning"] = sd.HealthLog.CriticalWarning
+ out["media_errors"] = sd.HealthLog.MediaErrors
+ out["percentage_used"] = sd.HealthLog.Percentage_used
+ } else {
+ out["temperature"] = sd.Temperature.Current
+ out["power_on_time"] = sd.PowerOnTime.Hours
+ out["critical_warning"] = 0
+ out["media_errors"] = 0
+ out["percentage_used"] = 0
+ }
+
+ for _, a := range sd.SmartAttributes.Table {
+ if a.Name == unknownAttrName {
+ continue
}
- default:
- return nil, zbxerr.ErrorUnsupportedMetric
+ out[strings.ToLower(a.Name)] = singleRequestAttribute{a.Raw.Value, a.Raw.Str}
}
- return string(jsonArray), nil
+ return
+}
+
+// setSelfTest determines if device is self test capable and if the test is passed.
+func setSelfTest(sd singleDevice) *bool {
+ if sd.Data.Capabilities.SelfTestsSupported {
+ return &sd.Data.SelfTest.Status.Passed
+ }
+
+ return nil
}
// setDiskFields goes through provided device json map and sets disk_name
@@ -242,6 +390,18 @@ func getAttributeType(devType string, rate int, tables []table) string {
return getTypeByRateAndAttr(rate, tables)
}
+func getAttributes(in deviceParser) (out string) {
+ for _, table := range in.SmartAttributes.Table {
+ if table.Attrname == unknownAttrName {
+ continue
+ }
+
+ out = out + " " + table.Attrname
+ }
+
+ return strings.TrimSpace(out)
+}
+
func getType(devType string, rate int, tables []table) string {
switch devType {
case nvmeType:
diff --git a/src/go/plugins/smart/smart_nix.go b/src/go/plugins/smart/smart_nix.go
index 447406c1d3b..5e87a39de9c 100644
--- a/src/go/plugins/smart/smart_nix.go
+++ b/src/go/plugins/smart/smart_nix.go
@@ -39,7 +39,7 @@ func (p *Plugin) executeSmartctl(args string, strict bool) ([]byte, error) {
var out string
var err error
- executable := fmt.Sprintf("sudo %s %s", path, args)
+ executable := fmt.Sprintf("sudo -n %s %s", path, args)
p.Tracef("executing smartctl command: %s", executable)
diff --git a/src/go/plugins/smart/smart_test.go b/src/go/plugins/smart/smart_test.go
index eb73c16c8c6..bce0bc96f34 100644
--- a/src/go/plugins/smart/smart_test.go
+++ b/src/go/plugins/smart/smart_test.go
@@ -25,20 +25,390 @@ import (
"testing"
)
+const (
+ nvme = `{
+ "smartctl": {
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/nvme0",
+ "type": "nvme"
+ },
+ "model_name": "INTEL SSDPEKNW512G8H",
+ "serial_number": "BTNH115603K7512A",
+ "firmware_version": "HPS1",
+ "smart_status": {
+ "passed": true
+ },
+ "nvme_smart_health_information_log": {
+ "critical_warning": 0,
+ "temperature": 25,
+ "percentage_used": 0,
+ "power_on_hours": 2222,
+ "media_errors": 0
+ }
+ }`
+
+ hdd = `{
+ "json_format_version": [
+ 1,
+ 0
+ ],
+ "smartctl": {
+ "version": [
+ 7,
+ 2
+ ],
+ "svn_revision": "5155",
+ "platform_info": "x86_64-linux-5.13.0-30-generic",
+ "build_info": "(local build)",
+ "argv": [
+ "smartctl",
+ "-a",
+ "-j",
+ "/dev/sda"
+ ],
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/sda",
+ "info_name": "/dev/sda [SAT]",
+ "type": "sat",
+ "protocol": "ATA"
+ },
+ "model_family": "Seagate Surveillance",
+ "model_name": "ST1000VX000-1ES162",
+ "serial_number": "Z4Y7SJBD",
+ "wwn": {
+ "naa": 5,
+ "oui": 3152,
+ "id": 2071267458
+ },
+ "firmware_version": "CV26",
+ "rotation_rate": 7200,
+ "ata_smart_data": {
+ "self_test": {
+ "status": {
+ "value": 0,
+ "string": "completed without error",
+ "passed": true
+ }
+ },
+ "capabilities": {
+ "self_tests_supported": true
+ }
+ },
+ "ata_smart_attributes": {
+ "table": [
+ {
+ "id": 1,
+ "name": "Raw_Read_Error_Rate",
+ "raw": {
+ "value": 182786912,
+ "string": "182786912"
+ }
+ },
+ {
+ "id": 3,
+ "name": "Spin_Up_Time",
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ }
+ ]
+ },
+ "power_on_time": {
+ "hours": 39153
+ },
+ "temperature": {
+ "current": 30
+ }
+ }`
+
+ ssd = `
+ {
+ "json_format_version": [
+ 1,
+ 0
+ ],
+ "smartctl": {
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/sda",
+ "info_name": "/dev/sda",
+ "type": "ata",
+ "protocol": "ATA"
+ },
+ "model_name": "TS128GMTS800",
+ "serial_number": "D486530350",
+ "firmware_version": "O1225G",
+ "rotation_rate": 0,
+ "smart_status": {
+ "passed": true
+ },
+ "ata_smart_data": {
+ "self_test": {
+ "status": {
+ "passed": true
+ }
+ },
+ "capabilities": {
+ "values": [
+ 113,
+ 2
+ ],
+ "self_tests_supported": true
+ }
+ },
+ "ata_smart_attributes": {
+ "table": [
+ {
+ "name": "Raw_Read_Error_Rate",
+ "value": 100,
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ },
+ {
+ "name": "Reallocated_Sector_Ct",
+ "raw": {
+ "value": 10,
+ "string": "10"
+ }
+ }
+ ]
+ },
+ "power_on_time": {
+ "hours": 732
+ },
+ "temperature": {
+ "current": 18
+ }
+ }`
+
+ ssdUnknown = `
+ {
+ "json_format_version": [
+ 1,
+ 0
+ ],
+ "smartctl": {
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/sda",
+ "info_name": "/dev/sda",
+ "type": "ata",
+ "protocol": "ATA"
+ },
+ "model_name": "TS128GMTS800",
+ "serial_number": "D486530350",
+ "firmware_version": "O1225G",
+ "rotation_rate": 0,
+ "smart_status": {
+ "passed": true
+ },
+ "ata_smart_data": {
+ "self_test": {
+ "status": {
+ "passed": true
+ }
+ },
+ "capabilities": {
+ "values": [
+ 113,
+ 2
+ ],
+ "self_tests_supported": true
+ }
+ },
+ "ata_smart_attributes": {
+ "table": [
+ {
+ "name": "Raw_Read_Error_Rate",
+ "value": 100,
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ },
+ {
+ "name": "Unknown_Attribute",
+ "value": 0,
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ },
+ {
+ "name": "Reallocated_Sector_Ct",
+ "raw": {
+ "value": 10,
+ "string": "10"
+ }
+ }
+ ]
+ },
+ "power_on_time": {
+ "hours": 732
+ },
+ "temperature": {
+ "current": 18
+ }
+ }`
+)
+
var (
table1 = table{"test1", 1, 11}
table2 = table{"test2", 2, 22}
table3 = table{"test3", 3, 33}
table4 = table{"test4", 4, 44}
attrTable = table{"Spin_Up_Time", 5, 55}
+ unknown = table{"Unknown_Attribute", 0, 0}
)
+func Test_setSingleDiskFields(t *testing.T) {
+ var nilReference *bool
+
+ selftestSuccess := true
+
+ type args struct {
+ dev []byte
+ }
+ tests := []struct {
+ name string
+ args args
+ wantOut map[string]interface{}
+ wantErr bool
+ }{
+ {
+ "nvme_device",
+ args{[]byte(nvme)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "nvme",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "HPS1",
+ "media_errors": 0,
+ "model_name": "INTEL SSDPEKNW512G8H",
+ "percentage_used": 0,
+ "power_on_time": 2222,
+ "self_test_passed": nilReference,
+ "serial_number": "BTNH115603K7512A",
+ "temperature": 25,
+ },
+ false,
+ },
+ {
+ "hdd_device",
+ args{[]byte(hdd)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "hdd",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "CV26",
+ "media_errors": 0,
+ "model_name": "ST1000VX000-1ES162",
+ "percentage_used": 0,
+ "power_on_time": 39153,
+ "self_test_passed": &selftestSuccess,
+ "serial_number": "Z4Y7SJBD",
+ "temperature": 30,
+ "raw_read_error_rate": singleRequestAttribute{
+ Value: 182786912,
+ Raw: "182786912",
+ },
+ "spin_up_time": singleRequestAttribute{
+ Value: 0,
+ Raw: "0",
+ },
+ },
+ false,
+ },
+ {
+ "ssd_device",
+ args{[]byte(ssd)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "ssd",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "O1225G",
+ "media_errors": 0,
+ "model_name": "TS128GMTS800",
+ "percentage_used": 0,
+ "power_on_time": 732,
+ "self_test_passed": &selftestSuccess,
+ "serial_number": "D486530350",
+ "temperature": 18,
+ "raw_read_error_rate": singleRequestAttribute{
+ Value: 0,
+ Raw: "0",
+ },
+ "reallocated_sector_ct": singleRequestAttribute{
+ Value: 10,
+ Raw: "10",
+ },
+ },
+ false,
+ },
+ {
+ "ssd_device_with_unknown_attribute",
+ args{[]byte(ssdUnknown)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "ssd",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "O1225G",
+ "media_errors": 0,
+ "model_name": "TS128GMTS800",
+ "percentage_used": 0,
+ "power_on_time": 732,
+ "self_test_passed": &selftestSuccess,
+ "serial_number": "D486530350",
+ "temperature": 18,
+ "raw_read_error_rate": singleRequestAttribute{
+ Value: 0,
+ Raw: "0",
+ },
+ "reallocated_sector_ct": singleRequestAttribute{
+ Value: 10,
+ Raw: "10",
+ },
+ },
+ false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotOut, err := setSingleDiskFields(tt.args.dev)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("setSingleDiskFields() error = %v, wantErr %v", err, tt.wantErr)
+
+ return
+ }
+
+ if !reflect.DeepEqual(gotOut, tt.wantOut) {
+ t.Errorf("setSingleDiskFields() = %v, want %v", gotOut, tt.wantOut)
+ }
+ })
+ }
+}
+
func Test_setDiskFields(t *testing.T) {
- //nolint:lll
- jsonSdaStr := `{"device": {"name": "/dev/sda","info_name": "/dev/sda [SAT]","type": "sat","protocol": "ATA"},"rotation_rate": 0}`
- //nolint:lll
+ jsonSdaStr := `{
+ "device": {"name": "/dev/sda","info_name": "/dev/sda [SAT]","type": "sat","protocol": "ATA"},"rotation_rate": 0
+ }`
sdaOutStr := map[string]interface{}{
- "device": map[string]interface{}{"name": "/dev/sda", "info_name": "/dev/sda [SAT]", "type": "sat", "protocol": "ATA"},
+ "device": map[string]interface{}{
+ "name": "/dev/sda", "info_name": "/dev/sda [SAT]", "type": "sat", "protocol": "ATA",
+ },
"disk_name": "sda", "disk_type": "ssd", "rotation_rate": 0,
}
@@ -196,6 +566,50 @@ func Test_getAttributeType(t *testing.T) {
}
}
+func Test_getAttributes(t *testing.T) {
+ type args struct {
+ in deviceParser
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ "attributes_set",
+ args{deviceParser{SmartAttributes: smartAttributes{Table: []table{table1, table2}}}},
+ "test1 test2",
+ },
+ {
+ "attributes_table_empty",
+ args{deviceParser{SmartAttributes: smartAttributes{Table: []table{}}}},
+ "",
+ },
+ {
+ "unknown_attributes_table_empty",
+ args{deviceParser{SmartAttributes: smartAttributes{Table: []table{table1, unknown, table2}}}},
+ "test1 test2",
+ },
+ {
+ "attributes_missing",
+ args{deviceParser{}},
+ "",
+ },
+ {
+ "parser_missing",
+ args{},
+ "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := getAttributes(tt.args.in); got != tt.want {
+ t.Errorf("getAttributes() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
func Test_getType(t *testing.T) {
type args struct {
devType string
diff --git a/src/go/plugins/smart/smartfs.go b/src/go/plugins/smart/smartfs.go
index a01b87c4422..b695e853ba4 100644
--- a/src/go/plugins/smart/smartfs.go
+++ b/src/go/plugins/smart/smartfs.go
@@ -42,7 +42,8 @@ const (
ssdType = "ssd"
hddType = "hdd"
- spinUpAttrName = "Spin_Up_Time"
+ spinUpAttrName = "Spin_Up_Time"
+ unknownAttrName = "Unknown_Attribute"
ataSmartAttrFieldName = "ata_smart_attributes"
ataSmartAttrTableFieldName = "table"
@@ -68,12 +69,79 @@ type device struct {
DeviceType string `json:"{#DISKTYPE}"`
Model string `json:"{#MODEL}"`
SerialNumber string `json:"{#SN}"`
+ Path string `json:"{#PATH}"`
+ RaidType string `json:"{#RAIDTYPE}"`
+ Attributes string `json:"{#ATTRIBUTES}"`
}
type jsonDevice struct {
serialNumber string
jsonData string
}
+type singleDevice struct {
+ DiskType string `json:"disk_type"`
+ Firmware string `json:"firmware_version"`
+ ModelName string `json:"model_name"`
+ SerialNumber string `json:"serial_number"`
+ Smartctl smartctlField `json:"smartctl"`
+ HealthLog healthLog `json:"nvme_smart_health_information_log"`
+ SmartAttributes singelRequestTables `json:"ata_smart_attributes"`
+ Data ataData `json:"ata_smart_data"`
+ Temperature temperature `json:"temperature"`
+ PowerOnTime power `json:"power_on_time"`
+ Err string `json:"-"`
+ SelfTest bool `json:"-"`
+}
+
+type healthLog struct {
+ Temperature int `json:"temperature"`
+ PowerOnTime int `json:"power_on_hours"`
+ CriticalWarning int `json:"critical_warning"`
+ MediaErrors int `json:"media_errors"`
+ Percentage_used int `json:"percentage_used"`
+}
+
+type temperature struct {
+ Current int `json:"current"`
+}
+
+type power struct {
+ Hours int `json:"hours"`
+}
+
+type singelRequestTables struct {
+ Table []singelRequestRaw `json:"table"`
+}
+
+type singelRequestRaw struct {
+ Name string `json:"name"`
+ Raw rawField `json:"raw"`
+}
+
+type singleRequestAttribute struct {
+ Value int `json:"value"`
+ Raw string `json:"raw"`
+}
+
+type rawField struct {
+ Value int `json:"value"`
+ Str string `json:"string"`
+}
+
+type ataData struct {
+ SelfTest selfTest `json:"self_test"`
+ Capabilities capabilities `json:"capabilities"`
+}
+type capabilities struct {
+ SelfTestsSupported bool `json:"self_tests_supported"`
+}
+type selfTest struct {
+ Status status `json:"status"`
+}
+type status struct {
+ Passed bool `json:"passed"`
+}
+
type attribute struct {
Name string `json:"{#NAME}"`
DeviceType string `json:"{#DISKTYPE}"`
@@ -96,6 +164,8 @@ type deviceInfo struct {
Name string `json:"name"`
InfoName string `json:"info_name"`
DevType string `json:"type"`
+ name string `json:"-"`
+ raidType string `json:"-"`
}
type smartctl struct {
@@ -184,7 +254,17 @@ func (p *Plugin) execute(jsonRunner bool) (*runner, error) {
return r, err
}
-//executeBase executed runners for basic devices retrived from smartctl
+// executeSingle returns device data for single device from smartctl based on provided path.
+func (p *Plugin) executeSingle(path string) (device []byte, err error) {
+ device, err = p.executeSmartctl(fmt.Sprintf("-a %s -j", path), false)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to execute smartctl: %w.", err)
+ }
+
+ return
+}
+
+//executeBase executed runners for basic devices retrieved from smartctl.
func (r *runner) executeBase(basicDev []deviceInfo, jsonRunner bool) error {
r.startBasicRunners(jsonRunner)
@@ -197,7 +277,7 @@ func (r *runner) executeBase(basicDev []deviceInfo, jsonRunner bool) error {
return r.waitForExecution()
}
-//executeRaids executes runners for raid devices (except megaraid) retrived from smartctl
+//executeRaids executes runners for raid devices (except megaraid) retrieved from smartctl
func (r *runner) executeRaids(raids []deviceInfo, jsonRunner bool) {
raidTypes := []string{"3ware", "areca", "cciss", "sat"}
@@ -216,7 +296,7 @@ func (r *runner) executeRaids(raids []deviceInfo, jsonRunner bool) {
r.waitForRaidExecution(r.raidDone)
}
-//executeMegaRaids executes runners for megaraid devices retrived from smartctl
+//executeMegaRaids executes runners for megaraid devices retrieved from smartctl
func (r *runner) executeMegaRaids(megaraids []deviceInfo, jsonRunner bool) {
r.megaraids = make(chan raidParameters, len(megaraids))
@@ -381,6 +461,8 @@ func (r *runner) getBasicDevices(jsonRunner bool) {
return
}
+ dp.Info.name = name
+
r.mux.Lock()
if jsonRunner {
@@ -454,10 +536,14 @@ runner:
}
if dp.SmartStatus != nil {
+ dp.Info.name = raid.name
+
if raid.rType == satType {
dp.Info.Name = fmt.Sprintf("%s %s", raid.name, raid.rType)
+ dp.Info.raidType = raid.rType
} else {
dp.Info.Name = fmt.Sprintf("%s %s,%d", raid.name, raid.rType, i)
+ dp.Info.raidType = fmt.Sprintf("%s,%d", raid.rType, i)
}
if r.setRaidDevices(dp, device, raid.rType, jsonRunner) {
@@ -523,6 +609,8 @@ func (r *runner) getMegaRaidDevices(jsonRunner bool) {
}
dp.Info.Name = fmt.Sprintf("%s %s", raid.name, raid.rType)
+ dp.Info.name = raid.name
+ dp.Info.raidType = raid.rType
r.setRaidDevices(dp, device, raid.rType, jsonRunner)
}
@@ -619,32 +707,41 @@ func (dp *deviceParser) checkErr() (err error) {
func (p *Plugin) getDevices() (basic, raid, megaraid []deviceInfo, err error) {
basicTmp, err := p.scanDevices("--scan -j")
if err != nil {
- return nil, nil, nil, fmt.Errorf("Failed to scan for devices: %s.", err)
+ return nil, nil, nil, fmt.Errorf("Failed to scan for devices: %w.", err)
}
raidTmp, err := p.scanDevices("--scan -d sat -j")
if err != nil {
- return nil, nil, nil, fmt.Errorf("Failed to scan for sat devices: %s.", err)
+ return nil, nil, nil, fmt.Errorf("Failed to scan for sat devices: %w.", err)
}
-raid:
- for _, tmp := range basicTmp {
- for _, r := range raidTmp {
+ basic, raid, megaraid = formatDeviceOutput(basicTmp, raidTmp)
+
+ return
+}
+
+// formatDeviceOutput removes raid devices from basic device list and separates megaraid devices from the rest of raid
+// devices.
+func formatDeviceOutput(basic, raid []deviceInfo) (basicDev, raidDev, megaraidDev []deviceInfo) {
+loop:
+ for _, tmp := range basic {
+ for _, r := range raid {
if tmp.Name == r.Name {
- continue raid
+ continue loop
}
}
- basic = append(basic, tmp)
+ basicDev = append(basicDev, tmp)
}
- for _, r := range raidTmp {
+ for _, r := range raid {
if strings.Contains(r.DevType, "megaraid") {
- megaraid = append(megaraid, r)
+ megaraidDev = append(megaraidDev, r)
+
continue
}
- raid = append(raid, r)
+ raidDev = append(raidDev, r)
}
return
diff --git a/src/libs/zbxalgo/linked_list.c b/src/libs/zbxalgo/linked_list.c
index 8964958edcb..6a281cdd15b 100644
--- a/src/libs/zbxalgo/linked_list.c
+++ b/src/libs/zbxalgo/linked_list.c
@@ -334,3 +334,34 @@ void zbx_list_iterator_update(zbx_list_iterator_t *iterator)
if (NULL != iterator->current)
iterator->next = iterator->current->next;
}
+
+
+/******************************************************************************
+ * *
+ * Purpose: removes next iterator value from list *
+ * *
+ * Parameters: iterator - [IN] list iterator *
+ * *
+ * Return value: The data held by the removed item. *
+ * *
+ ******************************************************************************/
+void *zbx_list_iterator_remove_next(zbx_list_iterator_t *iterator)
+{
+ zbx_list_item_t *next;
+ void *data;
+
+ if (NULL == iterator->current || NULL == iterator->next)
+ return NULL;
+
+ next = iterator->next;
+ data = next->data;
+
+ if (iterator->list->tail == next)
+ iterator->list->tail = iterator->current;
+
+ iterator->current->next = next->next;
+ iterator->next = next->next;
+ iterator->list->mem_free_func(next);
+
+ return data;
+}
diff --git a/src/libs/zbxnix/daemon.c b/src/libs/zbxnix/daemon.c
index 07a60eed431..236ee74736e 100644
--- a/src/libs/zbxnix/daemon.c
+++ b/src/libs/zbxnix/daemon.c
@@ -28,6 +28,11 @@
#include "control.h"
#include "pid.h"
+#if defined(__linux__)
+#define ZBX_PID_FILE_TIMEOUT 20
+#define ZBX_PID_FILE_SLEEP_TIME 100000000
+#endif
+
char *CONFIG_PID_FILE = NULL;
static int parent_pid = -1;
@@ -374,8 +379,29 @@ int zbx_daemon_start(int allow_root, const char *user, unsigned int flags)
if (0 == (flags & ZBX_TASK_FLAG_FOREGROUND))
{
- if (0 != zbx_fork())
+ pid_t child_pid;
+
+ if(0 != (child_pid = zbx_fork()))
+ {
+#if defined(__linux__)
+ if (0 < child_pid)
+ {
+ int pid_file_timeout = ZBX_PID_FILE_TIMEOUT;
+ zbx_stat_t stat_buff;
+ struct timespec ts = {0, ZBX_PID_FILE_SLEEP_TIME};
+
+ /* wait for the forked child to create pid file */
+ while (0 < pid_file_timeout && 0 != zbx_stat(CONFIG_PID_FILE, &stat_buff))
+ {
+ pid_file_timeout--;
+ nanosleep(&ts, NULL);
+ }
+ }
+#else
+ ZBX_UNUSED(child_pid);
+#endif
exit(EXIT_SUCCESS);
+ }
setsid();
diff --git a/src/zabbix_server/ha/ha_manager.c b/src/zabbix_server/ha/ha_manager.c
index bc4c64cc628..f162a2cdf39 100644
--- a/src/zabbix_server/ha/ha_manager.c
+++ b/src/zabbix_server/ha/ha_manager.c
@@ -715,6 +715,63 @@ finish:
/******************************************************************************
* *
+ * Purpose: check for active and standby node availability and update *
+ * unavailable nodes accordingly *
+ * *
+ ******************************************************************************/
+static int ha_db_check_unavailable_nodes(zbx_ha_info_t *info, zbx_vector_ha_node_t *nodes, int db_time)
+{
+ int i, ret = SUCCEED;
+
+ zbx_vector_str_t unavailable_nodes;
+
+ zbx_vector_str_create(&unavailable_nodes);
+
+ for (i = 0; i < nodes->values_num; i++)
+ {
+ if (SUCCEED == zbx_cuid_compare(nodes->values[i]->ha_nodeid, info->ha_nodeid))
+ continue;
+
+ if (ZBX_NODE_STATUS_STANDBY != nodes->values[i]->status &&
+ ZBX_NODE_STATUS_ACTIVE != nodes->values[i]->status)
+ {
+ continue;
+ }
+
+
+ if (db_time >= nodes->values[i]->lastaccess + info->failover_delay)
+ {
+ zbx_vector_str_append(&unavailable_nodes, nodes->values[i]->ha_nodeid.str);
+
+ zbx_audit_ha_create_entry(AUDIT_ACTION_UPDATE, nodes->values[i]->ha_nodeid.str,
+ nodes->values[i]->name);
+ zbx_audit_ha_update_field_int(nodes->values[i]->ha_nodeid.str, ZBX_AUDIT_HA_STATUS,
+ nodes->values[i]->status, ZBX_NODE_STATUS_UNAVAILABLE);
+ }
+ }
+
+ if (0 != unavailable_nodes.values_num)
+ {
+ char *sql = NULL;
+ size_t sql_alloc = 0, sql_offset = 0;
+
+ zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, "update ha_node set status=%d where",
+ ZBX_NODE_STATUS_UNAVAILABLE);
+
+ DBadd_str_condition_alloc(&sql, &sql_alloc, &sql_offset, "ha_nodeid",
+ (const char **)unavailable_nodes.values, unavailable_nodes.values_num);
+
+ ret = ha_db_execute(info, "%s", sql);
+ zbx_free(sql);
+ }
+
+ zbx_vector_str_destroy(&unavailable_nodes);
+
+ return ret;
+}
+
+/******************************************************************************
+ * *
* Purpose: register server node *
* *
* Return value: SUCCEED - node was registered or database was offline *
@@ -810,6 +867,9 @@ static void ha_db_register_node(zbx_ha_info_t *info)
ha_db_execute(info, "delete from ha_node where name<>''");
}
+ if (ZBX_HA_IS_CLUSTER() && ZBX_NODE_STATUS_ERROR != info->ha_status && ZBX_NODE_STATUS_ACTIVE == ha_status)
+ ha_db_check_unavailable_nodes(info, &nodes, db_time);
+
ha_flush_audit(info);
zbx_free(sql);
@@ -841,49 +901,11 @@ finish:
******************************************************************************/
static int ha_check_standby_nodes(zbx_ha_info_t *info, zbx_vector_ha_node_t *nodes, int db_time)
{
- int i, ret = SUCCEED;
- zbx_vector_str_t unavailable_nodes;
+ int ret;
zbx_audit_init(info->auditlog);
- zbx_vector_str_create(&unavailable_nodes);
-
- for (i = 0; i < nodes->values_num; i++)
- {
- if (nodes->values[i]->status != ZBX_NODE_STATUS_STANDBY)
- continue;
-
- if (db_time >= nodes->values[i]->lastaccess + info->failover_delay)
- {
- zbx_vector_str_append(&unavailable_nodes, nodes->values[i]->ha_nodeid.str);
-
- zbx_audit_ha_create_entry(AUDIT_ACTION_UPDATE, nodes->values[i]->ha_nodeid.str,
- nodes->values[i]->name);
- zbx_audit_ha_update_field_int(nodes->values[i]->ha_nodeid.str, ZBX_AUDIT_HA_STATUS,
- nodes->values[i]->status, ZBX_NODE_STATUS_UNAVAILABLE);
- }
- }
-
- if (0 != unavailable_nodes.values_num)
- {
- char *sql = NULL;
- size_t sql_alloc = 0, sql_offset = 0;
-
- zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, "update ha_node set status=%d where",
- ZBX_NODE_STATUS_UNAVAILABLE);
-
- DBadd_str_condition_alloc(&sql, &sql_alloc, &sql_offset, "ha_nodeid",
- (const char **)unavailable_nodes.values, unavailable_nodes.values_num);
-
- if (SUCCEED != ha_db_execute(info, "%s", sql))
- ret = FAIL;
-
- zbx_free(sql);
- }
-
- zbx_vector_str_destroy(&unavailable_nodes);
-
- if (SUCCEED == ret)
+ if (SUCCEED == (ret = ha_db_check_unavailable_nodes(info, nodes, db_time)))
ha_flush_audit(info);
else
zbx_audit_clean();
diff --git a/src/zabbix_server/lld/lld_item.c b/src/zabbix_server/lld/lld_item.c
index 6215b2489b8..491a027e10c 100644
--- a/src/zabbix_server/lld/lld_item.c
+++ b/src/zabbix_server/lld/lld_item.c
@@ -1836,6 +1836,16 @@ static void lld_items_validate(zbx_uint64_t hostid, zbx_vector_ptr_t *items, zbx
dependent = (zbx_lld_item_t *)item->dependent_items.values[j];
dependent->flags &= ~ZBX_FLAG_LLD_ITEM_DISCOVERED;
}
+
+ continue;
+ }
+
+ if (0 != item->master_itemid && (FAIL != zbx_vector_ptr_bsearch(item_prototypes, &item->master_itemid,
+ ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC)))
+ {
+ item->flags &= ~ZBX_FLAG_LLD_ITEM_DISCOVERED;
+ *error = zbx_strdcatf(*error, "Cannot %s dependent item: master item is not discovered.\n",
+ (0 != item->itemid ? "update" : "create"));
}
}
diff --git a/src/zabbix_server/preprocessor/preproc_manager.c b/src/zabbix_server/preprocessor/preproc_manager.c
index 99d59c9ca72..1620793ee3f 100644
--- a/src/zabbix_server/preprocessor/preproc_manager.c
+++ b/src/zabbix_server/preprocessor/preproc_manager.c
@@ -25,6 +25,7 @@
#include "zbxlld.h"
#include "preprocessing.h"
#include "preproc_history.h"
+#include "preproc_manager.h"
extern ZBX_THREAD_LOCAL unsigned char process_type;
extern unsigned char program_type;
@@ -53,13 +54,18 @@ typedef enum
}
zbx_preprocessing_kind_t;
-typedef struct zbx_preprocessing_request_base
+typedef struct zbx_preprocessing_request_base zbx_preprocessing_request_base_t;
+
+ZBX_PTR_VECTOR_DECL(preprocessing_request_base, zbx_preprocessing_request_base_t *)
+ZBX_PTR_VECTOR_IMPL(preprocessing_request_base, zbx_preprocessing_request_base_t *)
+
+struct zbx_preprocessing_request_base
{
zbx_preprocessing_kind_t kind;
zbx_preprocessing_states_t state;
- struct zbx_preprocessing_request_base *pending; /* the request waiting on this request to complete */
-}
-zbx_preprocessing_request_base_t;
+ zbx_preprocessing_request_base_t *pending; /* the request waiting on this request to complete */
+ zbx_vector_preprocessing_request_base_t flush_queue; /* processed request waiting to be flushed */
+};
/* preprocessing request */
typedef struct preprocessing_request
@@ -291,9 +297,11 @@ static zbx_uint32_t preprocessor_create_task(zbx_preprocessing_manager_t *manage
static void preprocessor_set_request_state_done(zbx_preprocessing_manager_t *manager,
zbx_preprocessing_request_base_t *base, const zbx_list_item_t *queue_item)
{
- zbx_item_link_t *index, index_local;
- zbx_preprocessing_request_t *request;
- zbx_preprocessing_dep_request_t *dep_request;
+ zbx_item_link_t *index, index_local;
+ zbx_list_iterator_t iterator, next_iterator;
+ zbx_preprocessing_request_t *request;
+ zbx_preprocessing_dep_request_t *dep_request;
+ zbx_preprocessing_request_base_t *prev;
base->state = REQUEST_STATE_DONE;
@@ -320,6 +328,31 @@ static void preprocessor_set_request_state_done(zbx_preprocessing_manager_t *man
{
zbx_hashset_remove_direct(&manager->linked_items, index);
}
+
+ if (NULL == manager->queue.head)
+ return;
+
+ zbx_list_iterator_init(&manager->queue, &iterator);
+ if (iterator.next == queue_item)
+ return;
+
+ while (SUCCEED == zbx_list_iterator_next(&iterator))
+ {
+ if (iterator.next == queue_item)
+ break;
+ }
+
+ prev = (zbx_preprocessing_request_base_t *)iterator.current->data;
+ zbx_vector_preprocessing_request_base_append(&prev->flush_queue, base);
+
+ next_iterator = iterator;
+ if (SUCCEED == zbx_list_iterator_next(&next_iterator))
+ {
+ if (SUCCEED == zbx_list_iterator_equal(&next_iterator, &manager->priority_tail))
+ manager->priority_tail = iterator;
+ }
+
+ (void)zbx_list_iterator_remove_next(&iterator);
}
/******************************************************************************
@@ -623,6 +656,9 @@ static void preprocessor_free_request(zbx_preprocessing_request_base_t *base)
zbx_preprocessing_request_t *request;
zbx_preprocessing_dep_request_t *dep_request;
+ zbx_vector_preprocessing_request_base_clear_ext(&base->flush_queue, preprocessor_free_request);
+ zbx_vector_preprocessing_request_base_destroy(&base->flush_queue);
+
switch (base->kind)
{
case ZBX_PREPROC_ITEM:
@@ -708,6 +744,40 @@ static void preprocessor_flush_dep_results(zbx_preprocessing_manager_t *manager,
/******************************************************************************
* *
+ * Purpose: recursively flush processed request and the other processed *
+ * requests that were waiting on this request to be finished *
+ * *
+ * Parameters: manager - [IN] preprocessing manager *
+ * base - [IN] the preprocessing request *
+ * *
+ ******************************************************************************/
+static void preprocessing_flush_request(zbx_preprocessing_manager_t *manager,
+ zbx_preprocessing_request_base_t *base)
+{
+ zbx_preprocessing_request_t *request;
+ zbx_preprocessing_dep_request_t *dep_request;
+ int i;
+
+ switch (base->kind)
+ {
+ case ZBX_PREPROC_ITEM:
+ request = (zbx_preprocessing_request_t *)base;
+ preprocessor_flush_value(&request->value);
+ manager->processed_num++;
+ manager->queued_num--;
+ break;
+ case ZBX_PREPROC_DEPS:
+ dep_request = (zbx_preprocessing_dep_request_t *)base;
+ preprocessor_flush_dep_results(manager, dep_request);
+ break;
+ }
+
+ for (i = 0; i < base->flush_queue.values_num; i++)
+ preprocessing_flush_request(manager, base->flush_queue.values[i]);
+}
+
+/******************************************************************************
+ * *
* Purpose: add all sequential processed values from beginning of the queue *
* to the local history cache *
* *
@@ -716,8 +786,6 @@ static void preprocessor_flush_dep_results(zbx_preprocessing_manager_t *manager,
******************************************************************************/
static void preprocessing_flush_queue(zbx_preprocessing_manager_t *manager)
{
- zbx_preprocessing_request_t *request;
- zbx_preprocessing_dep_request_t *dep_request;
zbx_preprocessing_request_base_t *base;
zbx_list_iterator_t iterator;
@@ -729,19 +797,7 @@ static void preprocessing_flush_queue(zbx_preprocessing_manager_t *manager)
if (REQUEST_STATE_DONE != base->state)
break;
- switch (base->kind)
- {
- case ZBX_PREPROC_ITEM:
- request = (zbx_preprocessing_request_t *)base;
- preprocessor_flush_value(&request->value);
- manager->processed_num++;
- manager->queued_num--;
- break;
- case ZBX_PREPROC_DEPS:
- dep_request = (zbx_preprocessing_dep_request_t *)base;
- preprocessor_flush_dep_results(manager, dep_request);
- break;
- }
+ preprocessing_flush_request(manager, base);
if (SUCCEED == zbx_list_iterator_equal(&iterator, &manager->priority_tail))
zbx_list_iterator_clear(&manager->priority_tail);
@@ -883,8 +939,9 @@ static void preprocessor_enqueue(zbx_preprocessing_manager_t *manager, zbx_prepr
state = REQUEST_STATE_QUEUED;
request = (zbx_preprocessing_request_t *)zbx_malloc(NULL, sizeof(zbx_preprocessing_request_t));
- request->base.kind = ZBX_PREPROC_ITEM;
memset(request, 0, sizeof(zbx_preprocessing_request_t));
+ request->base.kind = ZBX_PREPROC_ITEM;
+ zbx_vector_preprocessing_request_base_create(&request->base.flush_queue);
memcpy(&request->value, value, sizeof(zbx_preproc_item_value_t));
request->base.state = state;
@@ -995,6 +1052,7 @@ static void preprocessor_enqueue_dependent(zbx_preprocessing_manager_t *manager,
dep_request->base.kind = ZBX_PREPROC_DEPS;
dep_request->base.state = REQUEST_STATE_QUEUED;
dep_request->base.pending = NULL;
+ zbx_vector_preprocessing_request_base_create(&dep_request->base.flush_queue);
dep_request->hostid = hostid;
dep_request->ts = NULL != ts ? *ts : (zbx_timespec_t){0, 0};