/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** 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; either version 2 of the License, or ** (at your option) any later version. ** ** 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ package redis import ( "bufio" "encoding/json" "regexp" "strings" "git.zabbix.com/ap/plugin-support/zbxerr" "github.com/mediocregopher/radix/v3" ) type infoSection string type infoKey string type infoKeySpace map[infoKey]interface{} type infoExtKey string type infoExtKeySpace map[infoExtKey]string type redisInfo map[infoSection]infoKeySpace var redisSlaveMetricRE = regexp.MustCompile(`^slave\d+`) // parseRedisInfo parses an output of 'INFO' command. // https://redis.io/commands/info func parseRedisInfo(info string) (res redisInfo, err error) { var ( section infoSection ) scanner := bufio.NewScanner(strings.NewReader(info)) res = make(redisInfo) for scanner.Scan() { line := scanner.Text() if len(line) == 0 { continue } // Names of sections are preceded by '#'. if line[0] == '#' { section = infoSection(line[2:]) if _, ok := res[section]; !ok { res[section] = make(infoKeySpace) } continue } // Each parameter represented in the 'key:value' format. kv := strings.SplitN(line, ":", 2) if len(kv) != 2 { continue } key := infoKey(kv[0]) value := strings.TrimSpace(kv[1]) // Followed sections has a bit more complicated format. // E.g: dbXXX: keys=XXX,expires=XXX if section == "Keyspace" || section == "Commandstats" || (section == "Replication" && redisSlaveMetricRE.MatchString(string(key))) { extKeySpace := make(infoExtKeySpace) for _, ksParams := range strings.Split(value, ",") { ksParts := strings.Split(ksParams, "=") extKeySpace[infoExtKey(ksParts[0])] = ksParts[1] } res[section][key] = extKeySpace continue } if len(section) == 0 { return nil, zbxerr.ErrorCannotParseResult } res[section][key] = value } if err = scanner.Err(); err != nil { return nil, err } if len(res) == 0 { return nil, zbxerr.ErrorEmptyResult } return res, nil } // infoHandler gets an output of 'INFO' command, parses it and returns it in JSON format. func infoHandler(conn redisClient, params map[string]string) (interface{}, error) { var res string section := infoSection(strings.ToLower(params["Section"])) if err := conn.Query(radix.Cmd(&res, "INFO", string(section))); err != nil { return nil, zbxerr.ErrorCannotFetchData.Wrap(err) } redisInfo, err := parseRedisInfo(res) if err != nil { return nil, err } jsonRes, err := json.Marshal(redisInfo) if err != nil { return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) } return string(jsonRes), nil }