Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gitlab-org/build/omnibus-mirror/redis_exporter_test
1 result
Show changes
Commits on Source (22)
Showing
with 799 additions and 206 deletions
Loading
Loading
@@ -14,13 +14,13 @@
version = "v1.17.0"
 
[[projects]]
name = "github.com/garyburd/redigo"
name = "github.com/gomodule/redigo"
packages = [
"internal",
"redis"
]
revision = "d1ed5c67e5794de818ea85e6b522fda02623a484"
version = "v1.4.0"
revision = "9c11da706d9b7902c6da69c592f75637793fe121"
version = "v2.0.0"
 
[[projects]]
branch = "master"
Loading
Loading
Loading
Loading
@@ -8,8 +8,8 @@
version = "1.17.0"
 
[[constraint]]
name = "github.com/garyburd/redigo"
version = "1.4.0"
name = "github.com/gomodule/redigo"
version = "2.0.0"
 
[[constraint]]
name = "github.com/prometheus/client_golang"
Loading
Loading
Loading
Loading
@@ -43,6 +43,40 @@ To run on Cloud Foundry, use:
cf push -f contrib/manifest.yml
```
 
#### Run on Openshift
In order to deploy the exporter on Openshift environment.
```
oc project <myproject>
oc process -f https://raw.githubusercontent.com/ivanovaleksandar/redis_exporter/master/contrib/openshift-template.yaml \
-p REDIS_ADDR=<redis-service>:<redis-port> \
-p REDIS_PASSWORD=<redis-pass> \
-p REDIS_ALIAS=<redis-alias> \
-p REDIS_FILE=<redis-file> \
| oc create -f -
```
*NOTE*: Some of the parameters can be ommited if no authentication is used or the default redis config is applied.
```
oc process -f https://raw.githubusercontent.com/ivanovaleksandar/redis_exporter/contrib/openshift-template.yaml \
-p REDIS_ADDR=<redis-service>:<redis-port> \
| oc create -f -
```
If you are running Prometheus on Openshift on the same cluster, **target** in `prometheus.yml` should point to the correct service name of the exporter
```
scrape_configs:
...
- job_name: redis_exporter
static_configs:
- targets: ['<redis-exporter.myproject.svc>:9121']
...
```
 
### Flags
 
Loading
Loading
Loading
Loading
@@ -2,33 +2,20 @@
 
export CGO_ENABLED=0
 
echo "Building binaries"
echo ""
echo $GO_LDFLAGS
gox -rebuild --osarch="darwin/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.darwin-amd64.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="darwin/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.darwin-386.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="linux/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.linux-amd64.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="linux/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.linux-386.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="netbsd/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.netbsd-amd64.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="netbsd/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.netbsd-386.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="windows/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && zip -9 redis_exporter-$CIRCLE_TAG.windows-amd64.zip redis_exporter.exe && rm redis_exporter.exe && cd ..
gox -rebuild --osarch="windows/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && zip -9 redis_exporter-$CIRCLE_TAG.windows-386.zip redis_exporter.exe && rm redis_exporter.exe && cd ..
echo "Upload to Github"
ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace $CIRCLE_TAG dist/
gox --osarch="linux/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter"
 
echo "Build Docker images"
 
docker version
 
gox --osarch="linux/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter"
echo "docker login"
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
docker info
echo "docker login done"
 
echo "Build Docker images"
docker build --rm=false -t "21zoo/redis_exporter:$CIRCLE_TAG" .
docker build --rm=false -t "21zoo/redis_exporter:latest" .
 
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
docker push "21zoo/redis_exporter:latest"
docker push "21zoo/redis_exporter:$CIRCLE_TAG"
 
Loading
Loading
@@ -37,4 +24,22 @@ docker build --rm=false -t "oliver006/redis_exporter:latest" .
docker push "oliver006/redis_exporter:latest"
docker push "oliver006/redis_exporter:$CIRCLE_TAG"
 
echo "Building binaries"
echo ""
echo $GO_LDFLAGS
gox -rebuild --osarch="darwin/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.darwin-amd64.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="darwin/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.darwin-386.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="linux/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.linux-amd64.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="linux/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.linux-386.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="netbsd/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.netbsd-amd64.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="netbsd/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && tar -cvzf redis_exporter-$CIRCLE_TAG.netbsd-386.tar.gz redis_exporter && rm redis_exporter && cd ..
gox -rebuild --osarch="windows/amd64" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && zip -9 redis_exporter-$CIRCLE_TAG.windows-amd64.zip redis_exporter.exe && rm redis_exporter.exe && cd ..
gox -rebuild --osarch="windows/386" -ldflags "$GO_LDFLAGS" -output "dist/redis_exporter" && cd dist && zip -9 redis_exporter-$CIRCLE_TAG.windows-386.zip redis_exporter.exe && rm redis_exporter.exe && cd ..
echo "Upload to Github"
ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace $CIRCLE_TAG dist/
echo "Done"
Loading
Loading
@@ -6,10 +6,11 @@ machine:
- /usr/bin/redis-server --port 6380:
background: true
environment:
SRC_LOCATION: "/home/ubuntu/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
COVERAGE_PROFILE: "/home/ubuntu/coverage.out"
GO_LDFLAGS: '-extldflags "-static" -X main.VERSION=$CIRCLE_TAG -X main.COMMIT_SHA1=$CIRCLE_SHA1 -X main.BUILD_DATE=$(date +%F-%T)'
MY_GO_VERSION: "1.9.2"
SRC_LOCATION: "/home/ubuntu/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
COVERAGE_PROFILE: "/home/ubuntu/coverage.out"
GO_LDFLAGS: '-extldflags "-static" -X main.VERSION=$CIRCLE_TAG -X main.COMMIT_SHA1=$CIRCLE_SHA1 -X main.BUILD_DATE=$(date +%F-%T)'
MY_GO_VERSION: "1.9.4"
REDIS_TEST_VERSION: "3.2.11"
 
dependencies:
pre:
Loading
Loading
@@ -17,16 +18,20 @@ dependencies:
- rm -rf /home/ubuntu/.go_project
- sudo service redis-server stop
- >
cd ~ && if [ ! -d "redis-3.2.10" ]; then
wget http://download.redis.io/releases/redis-3.2.10.tar.gz
tar xzf redis-3.2.10.tar.gz
cd redis-3.2.10 && make;
cd ~ && if [ ! -d "redis-$REDIS_TEST_VERSION" ]; then
wget http://download.redis.io/releases/redis-$REDIS_TEST_VERSION.tar.gz
tar xzf redis-$REDIS_TEST_VERSION.tar.gz
cd redis-$REDIS_TEST_VERSION && make;
fi
- cd ~/redis-3.2.10 && sudo make install
- cd ~/redis-$REDIS_TEST_VERSION && sudo make install
- sudo sed -i 's/bin/local\/bin/g' /etc/init/redis-server.conf
- sudo service redis-server start
#
# the next line will bring up a cluster of redis instances with slaves
# for addtl tests
- docker run -p 7000-7006:7000-7006 --name redis_cluster_test -d grokzen/redis-cluster
cache_directories:
- ~/redis-3.2.10
- ~/redis-$REDIS_TEST_VERSION
override:
- mkdir -p "/home/ubuntu/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME"
- ln -s $HOME/$CIRCLE_PROJECT_REPONAME $SRC_LOCATION
Loading
Loading
@@ -41,7 +46,7 @@ test:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
override:
- cd $SRC_LOCATION && go test -v -covermode=atomic -cover -race -coverprofile=$COVERAGE_PROFILE ./exporter/...
- cd $SRC_LOCATION && TEST_REDIS_CLUSTER_MASTER_URI=localhost:7000 TEST_REDIS_CLUSTER_SLAVE_URI=localhost:7005 go test -v -covermode=atomic -cover -race -coverprofile=$COVERAGE_PROFILE ./exporter/...
post:
- if [ -n "$COVERALLS_TOKEN" ]; then /home/ubuntu/.go_workspace/bin/goveralls -coverprofile=$COVERAGE_PROFILE -service=circle-ci -repotoken=$COVERALLS_TOKEN ; fi
 
Loading
Loading
apiVersion: v1
kind: Template
labels:
template: redis-exporter
app: redis-exporter
tier: redis
metadata:
annotations:
openshift.io/display-name: Openshift Redis Exporter deployment template
description: >-
Deploy a Redis exporter for Prometheus into a specific namespace together with image stream
tags: 'redis-exporter'
name: redis-exporter
parameters:
- name: NAME
description: The name of the application
displayName: Name
required: true
value: redis-exporter
- name: NAMESPACE
description: The namespace of the application
displayName: Namespace
required: true
- name: SOURCE_REPOSITORY_URL
description: The URL of the repository with your application source code.
displayName: Git Repository URL
required: true
value: 'https://github.com/ivanovaleksandar/redis_exporter.git'
- name: SOURCE_REPOSITORY_REF
description: Set the branch name if you are not using master branch
displayName: Git Reference
value: master
required: false
- name: REDIS_ADDR
description: Set the service names of the Redis instances that you like to export
displayName: Redis Addresses
required: true
- name: REDIS_PASSWORD
description: Set the password for the Redis instances that you like to export
displayName: Redis Password
required: false
- name: REDIS_ALIAS
description: Set the service alias of the Redis instances that you like to export
displayName: Redis Alias
required: false
- name: REDIS_FILE
description: Set the Redis file that contains one or more redis nodes, separated by newline
displayName: Redis file
required: false
objects:
- apiVersion: v1
kind: ImageStream
metadata:
generation: 2
labels:
app: redis-exporter
name: redis-exporter
name: redis-exporter
spec:
dockerImageRepository: oliver006/redis_exporter
- apiVersion: v1
kind: DeploymentConfig
metadata:
labels:
app: redis-exporter
name: redis-exporter
spec:
replicas: 1
selector:
app: redis-exporter
template:
metadata:
labels:
app: redis-exporter
spec:
containers:
- image: docker-registry.default.svc:5000/${NAMESPACE}/redis-exporter
imagePullPolicy: Always
name: redis-exporter
ports:
- containerPort: 9121
env:
- name: REDIS_ADDR
value: "${REDIS_ADDR}"
- name: REDIS_PASSWORD
value: "${REDIS_PASSWORD}"
- name: REDIS_ALIAS
value: "${REDIS_ALIAS}"
- name: REDIS_FILE
value: "${REDIS_FILE}"
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
securityContext: {}
terminationGracePeriodSeconds: 30
test: false
triggers: []
status: {}
- apiVersion: v1
kind: Service
metadata:
labels:
name: redis-exporter
role: service
name: redis-exporter
spec:
ports:
- port: 9121
targetPort: 9121
selector:
app: "redis-exporter"
\ No newline at end of file
redis://localhost:6379
redis://localhost:7000,password,alias
redis://localhost:7000,second-pwd
\ No newline at end of file
package exporter
import (
"encoding/csv"
"os"
"strings"
"github.com/cloudfoundry-community/go-cfenv"
log "github.com/sirupsen/logrus"
)
// loadRedisArgs loads the configuration for which redis hosts to monitor from either
// the environment or as passed from program arguments. Returns the list of host addrs,
// passwords, and their aliases.
func LoadRedisArgs(addr, password, alias, separator string) ([]string, []string, []string) {
if addr == "" {
addr = "redis://localhost:6379"
}
addrs := strings.Split(addr, separator)
passwords := strings.Split(password, separator)
for len(passwords) < len(addrs) {
passwords = append(passwords, passwords[0])
}
aliases := strings.Split(alias, separator)
for len(aliases) < len(addrs) {
aliases = append(aliases, aliases[0])
}
return addrs, passwords, aliases
}
// loadRedisFile opens the specified file and loads the configuration for which redis
// hosts to monitor. Returns the list of hosts addrs, passwords, and their aliases.
func LoadRedisFile(fileName string) ([]string, []string, []string, error) {
var addrs []string
var passwords []string
var aliases []string
file, err := os.Open(fileName)
if err != nil {
return nil, nil, nil, err
}
r := csv.NewReader(file)
r.FieldsPerRecord = -1
records, err := r.ReadAll()
if err != nil {
return nil, nil, nil, err
}
file.Close()
// For each line, test if it contains an optional password and alias and provide them,
// else give them empty strings
for _, record := range records {
length := len(record)
switch length {
case 3:
addrs = append(addrs, record[0])
passwords = append(passwords, record[1])
aliases = append(aliases, record[2])
case 2:
addrs = append(addrs, record[0])
passwords = append(passwords, record[1])
aliases = append(aliases, "")
case 1:
addrs = append(addrs, record[0])
passwords = append(passwords, "")
aliases = append(aliases, "")
}
}
return addrs, passwords, aliases, nil
}
func GetCloudFoundryRedisBindings() (addrs, passwords, aliases []string) {
if !cfenv.IsRunningOnCF() {
return
}
appEnv, err := cfenv.Current()
if err != nil {
log.Warnln("Unable to get current CF environment", err)
return
}
redisServices, err := appEnv.Services.WithTag("redis")
if err != nil {
log.Warnln("Error while getting redis services", err)
return
}
for _, redisService := range redisServices {
credentials := redisService.Credentials
addr := credentials["hostname"].(string) + ":" + credentials["port"].(string)
password := credentials["password"].(string)
alias := redisService.Name
addrs = append(addrs, addr)
passwords = append(passwords, password)
aliases = append(aliases, alias)
}
return
}
package exporter
import (
"log"
"testing"
)
func cmpStringArrays(a1, a2 []string) bool {
if len(a1) != len(a2) {
return false
}
for n := range a1 {
if a1[n] != a2[n] {
return false
}
}
return true
}
func TestLoadRedisArgs(t *testing.T) {
log.Println("TestLoadRedisArgs()")
tests := []struct {
addr, pwd, alias, sep string
wantAddr, wantPwds, wantAliases []string
}{
{
addr: "",
sep: ",",
wantAddr: []string{"redis://localhost:6379"},
wantPwds: []string{""},
wantAliases: []string{""},
},
{
addr: "redis://localhost:6379",
sep: ",",
wantAddr: []string{"redis://localhost:6379"},
wantPwds: []string{""},
wantAliases: []string{""},
},
{
addr: "redis://localhost:6379,redis://localhost:7000",
sep: ",",
wantAddr: []string{"redis://localhost:6379", "redis://localhost:7000"},
wantPwds: []string{"", ""},
wantAliases: []string{"", ""},
},
{
addr: "redis://localhost:6379,redis://localhost:7000,redis://localhost:7001",
sep: ",",
wantAddr: []string{"redis://localhost:6379", "redis://localhost:7000", "redis://localhost:7001"},
wantPwds: []string{"", "", ""},
wantAliases: []string{"", "", ""},
},
{
alias: "host-1",
sep: ",",
wantAddr: []string{"redis://localhost:6379"},
wantPwds: []string{""},
wantAliases: []string{"host-1"},
},
}
for _, test := range tests {
sep := test.sep
addrs, pwds, aliases := LoadRedisArgs(test.addr, test.pwd, test.alias, sep)
if !cmpStringArrays(addrs, test.wantAddr) {
t.Errorf("addrs not matching wantAliases, got: %v want: %v", addrs, test.wantAddr)
}
if !cmpStringArrays(pwds, test.wantPwds) {
t.Errorf("pwds not matching wantAliases, got: %v want: %v", pwds, test.wantPwds)
}
if !cmpStringArrays(aliases, test.wantAliases) {
t.Errorf("aliases not matching wantAliases, got: %v want: %v", aliases, test.wantAliases)
}
}
}
func TestLoadRedisFile(t *testing.T) {
if _, _, _, err := LoadRedisFile("doesnt-exist.txt"); err == nil {
t.Errorf("should have failed opening non existing file")
return
}
addrs, pwds, aliases, err := LoadRedisFile("../contrib/sample_redis_hosts_file.txt")
if err != nil {
t.Errorf("LoadRedisFile() failed, err: %s", err)
return
}
log.Printf("aliases: %v \n", aliases)
if !cmpStringArrays(addrs, []string{"redis://localhost:6379", "redis://localhost:7000", "redis://localhost:7000"}) {
t.Errorf("addrs not matching want")
}
if !cmpStringArrays(pwds, []string{"", "password", "second-pwd"}) {
t.Errorf("pwds not matching want")
}
if !cmpStringArrays(aliases, []string{"", "alias", ""}) {
t.Errorf("aliases not matching want")
}
}
func TestGetCloudFoundryRedisBindings(t *testing.T) {
GetCloudFoundryRedisBindings()
}
Loading
Loading
@@ -9,7 +9,7 @@ import (
"sync"
"time"
 
"github.com/garyburd/redigo/redis"
"github.com/gomodule/redigo/redis"
"github.com/prometheus/client_golang/prometheus"
prom_strutil "github.com/prometheus/prometheus/util/strutil"
log "github.com/sirupsen/logrus"
Loading
Loading
@@ -370,15 +370,21 @@ func parseConnectedSlaveString(slaveName string, slaveInfo string) (offset float
return
}
 
func extractConfigMetrics(config []string, addr string, alias string, scrapes chan<- scrapeResult) error {
func extractConfigMetrics(config []string, addr string, alias string, scrapes chan<- scrapeResult) (dbCount int, err error) {
if len(config)%2 != 0 {
return fmt.Errorf("invalid config: %#v", config)
return 0, fmt.Errorf("invalid config: %#v", config)
}
 
for pos := 0; pos < len(config)/2; pos++ {
strKey := config[pos*2]
strVal := config[pos*2+1]
 
if strKey == "databases" {
if dbCount, err = strconv.Atoi(strVal); err != nil {
return 0, fmt.Errorf("invalid config value for key databases: %#v", strVal)
}
}
// todo: we can add more configs to this map if there's interest
if !map[string]bool{
"maxmemory": true,
Loading
Loading
@@ -390,15 +396,16 @@ func extractConfigMetrics(config []string, addr string, alias string, scrapes ch
scrapes <- scrapeResult{Name: fmt.Sprintf("config_%s", config[pos*2]), Addr: addr, Alias: alias, Value: val}
}
}
return nil
return
}
 
func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes chan<- scrapeResult) error {
func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes chan<- scrapeResult, dbCount int, padDBKeyCounts bool) error {
cmdstats := false
lines := strings.Split(info, "\r\n")
 
instanceInfo := map[string]string{}
slaveInfo := map[string]string{}
handledDBs := map[string]bool{}
for _, line := range lines {
log.Debugf("info: %s", line)
if len(line) > 0 && line[0] == '#' {
Loading
Loading
@@ -414,19 +421,24 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
}
 
split := strings.Split(line, ":")
if _, ok := instanceInfoFields[split[0]]; ok {
instanceInfo[split[0]] = split[1]
if len(split) != 2 {
continue
}
fieldKey := split[0]
fieldValue := split[1]
if _, ok := instanceInfoFields[fieldKey]; ok {
instanceInfo[fieldKey] = fieldValue
continue
}
 
if _, ok := slaveInfoFields[split[0]]; ok {
slaveInfo[split[0]] = split[1]
if _, ok := slaveInfoFields[fieldKey]; ok {
slaveInfo[fieldKey] = fieldValue
continue
}
 
if split[0] == "master_link_status" {
if fieldKey == "master_link_status" {
e.metricsMtx.RLock()
if split[1] == "up" {
if fieldValue == "up" {
e.metrics["master_link_up"].WithLabelValues(addr, alias).Set(1)
} else {
e.metrics["master_link_up"].WithLabelValues(addr, alias).Set(0)
Loading
Loading
@@ -435,7 +447,7 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
continue
}
 
if slaveOffset, slaveIp, slaveState, ok := parseConnectedSlaveString(split[0], split[1]); ok {
if slaveOffset, slaveIp, slaveState, ok := parseConnectedSlaveString(fieldKey, fieldValue); ok {
e.metricsMtx.RLock()
e.metrics["connected_slave_offset"].WithLabelValues(
addr,
Loading
Loading
@@ -446,7 +458,7 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
e.metricsMtx.RUnlock()
}
 
if len(split) != 2 || !includeMetric(split[0]) {
if !includeMetric(fieldKey) {
continue
}
 
Loading
Loading
@@ -458,14 +470,14 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
cmdstat_set:calls=61,usec=3139,usec_per_call=51.46
cmdstat_setex:calls=75,usec=1260,usec_per_call=16.80
*/
frags := strings.Split(split[0], "_")
frags := strings.Split(fieldKey, "_")
if len(frags) != 2 {
continue
}
 
cmd := frags[1]
 
frags = strings.Split(split[1], ",")
frags = strings.Split(fieldValue, ",")
if len(frags) != 3 {
continue
}
Loading
Loading
@@ -487,16 +499,18 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
continue
}
 
if keysTotal, keysEx, avgTTL, ok := parseDBKeyspaceString(split[0], split[1]); ok {
scrapes <- scrapeResult{Name: "db_keys", Addr: addr, Alias: alias, DB: split[0], Value: keysTotal}
scrapes <- scrapeResult{Name: "db_keys_expiring", Addr: addr, Alias: alias, DB: split[0], Value: keysEx}
if keysTotal, keysEx, avgTTL, ok := parseDBKeyspaceString(fieldKey, fieldValue); ok {
dbName := fieldKey
scrapes <- scrapeResult{Name: "db_keys", Addr: addr, Alias: alias, DB: dbName, Value: keysTotal}
scrapes <- scrapeResult{Name: "db_keys_expiring", Addr: addr, Alias: alias, DB: dbName, Value: keysEx}
if avgTTL > -1 {
scrapes <- scrapeResult{Name: "db_avg_ttl_seconds", Addr: addr, Alias: alias, DB: split[0], Value: avgTTL}
scrapes <- scrapeResult{Name: "db_avg_ttl_seconds", Addr: addr, Alias: alias, DB: dbName, Value: avgTTL}
}
handledDBs[dbName] = true
continue
}
 
metricName := sanitizeMetricName(split[0])
metricName := sanitizeMetricName(fieldKey)
if newName, ok := metricMap[metricName]; ok {
metricName = newName
}
Loading
Loading
@@ -504,7 +518,7 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
var err error
var val float64
 
switch split[1] {
switch fieldValue {
 
case "ok":
val = 1
Loading
Loading
@@ -513,17 +527,27 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
val = 0
 
default:
val, err = strconv.ParseFloat(split[1], 64)
val, err = strconv.ParseFloat(fieldValue, 64)
 
}
if err != nil {
log.Debugf("couldn't parse %s, err: %s", split[1], err)
log.Debugf("couldn't parse %s, err: %s", fieldValue, err)
continue
}
 
scrapes <- scrapeResult{Name: metricName, Addr: addr, Alias: alias, Value: val}
}
 
if padDBKeyCounts {
for dbIndex := 0; dbIndex < dbCount; dbIndex++ {
dbName := "db" + strconv.Itoa(dbIndex)
if _, exists := handledDBs[dbName]; !exists {
scrapes <- scrapeResult{Name: "db_keys", Addr: addr, Alias: alias, DB: dbName, Value: 0}
scrapes <- scrapeResult{Name: "db_keys_expiring", Addr: addr, Alias: alias, DB: dbName, Value: 0}
}
}
}
e.metricsMtx.RLock()
e.metrics["instance_info"].WithLabelValues(
addr, alias,
Loading
Loading
@@ -546,6 +570,16 @@ func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes c
return nil
}
 
func doRedisCmd(c redis.Conn, cmd string, args ...interface{}) (reply interface{}, err error) {
log.Debugf("c.Do() - running command: %s %s", cmd, args)
defer log.Debugf("c.Do() - done")
res, err := c.Do(cmd, args...)
if err != nil {
log.Debugf("c.Do() - err: %s", err)
}
return res, err
}
func (e *Exporter) scrapeRedisHost(scrapes chan<- scrapeResult, addr string, idx int) error {
options := []redis.DialOption{
redis.DialConnectTimeout(5 * time.Second),
Loading
Loading
@@ -572,37 +606,52 @@ func (e *Exporter) scrapeRedisHost(scrapes chan<- scrapeResult, addr string, idx
}
 
if err != nil {
log.Printf("redis err: %s", err)
log.Debugf("aborting for addr: %s - redis err: %s", addr, err)
return err
}
 
defer c.Close()
log.Debugf("connected to: %s", addr)
 
dbCount := 0
if config, err := redis.Strings(c.Do("CONFIG", "GET", "*")); err == nil {
extractConfigMetrics(config, addr, e.redis.Aliases[idx], scrapes)
dbCount, err = extractConfigMetrics(config, addr, e.redis.Aliases[idx], scrapes)
if err != nil {
log.Errorf("Redis CONFIG err: %s", err)
return err
}
} else {
log.Debugf("Redis CONFIG err: %s", err)
}
 
info, err := redis.String(c.Do("INFO", "ALL"))
if err == nil {
e.extractInfoMetrics(info, addr, e.redis.Aliases[idx], scrapes)
} else {
infoAll, err := redis.String(doRedisCmd(c, "INFO", "ALL"))
if err != nil {
log.Errorf("Redis INFO err: %s", err)
return err
}
isClusterEnabled := strings.Contains(infoAll, "cluster_enabled:1")
 
if strings.Index(info, "cluster_enabled:1") != -1 {
info, err = redis.String(c.Do("CLUSTER", "INFO"))
if err != nil {
log.Errorf("redis err: %s", err)
if isClusterEnabled {
if clusterInfo, err := redis.String(doRedisCmd(c, "CLUSTER", "INFO")); err == nil {
e.extractInfoMetrics(clusterInfo, addr, e.redis.Aliases[idx], scrapes, dbCount, false)
// in cluster mode Redis only supports one database so no extra padding beyond that needed
dbCount = 1
} else {
e.extractInfoMetrics(info, addr, e.redis.Aliases[idx], scrapes)
log.Errorf("Redis CLUSTER INFO err: %s", err)
}
} else {
// in non-cluster mode, if dbCount is zero then "CONFIG" failed to retrieve a valid
// number of databases and we use the Redis config default which is 16
if dbCount == 0 {
dbCount = 16
}
}
 
if reply, err := c.Do("LATENCY", "LATEST"); err == nil {
e.extractInfoMetrics(infoAll, addr, e.redis.Aliases[idx], scrapes, dbCount, true)
if reply, err := doRedisCmd(c, "LATENCY", "LATEST"); err == nil {
var eventName string
var spikeLast, milliseconds, max int64
if tempVal, _ := reply.([]interface{}); len(tempVal) > 0 {
Loading
Loading
@@ -616,13 +665,14 @@ func (e *Exporter) scrapeRedisHost(scrapes chan<- scrapeResult, addr string, idx
}
}
 
log.Debugf("e.keys: %#v", e.keys)
for _, k := range e.keys {
if _, err := c.Do("SELECT", k.db); err != nil {
if _, err := doRedisCmd(c, "SELECT", k.db); err != nil {
continue
}
 
obtainedKeys := []string{}
if tempVal, err := redis.Strings(c.Do("KEYS", k.key)); err == nil && tempVal != nil {
if tempVal, err := redis.Strings(doRedisCmd(c, "KEYS", k.key)); err == nil && tempVal != nil {
for _, tempKey := range tempVal {
log.Debugf("Append result: %s", tempKey)
obtainedKeys = append(obtainedKeys, tempKey)
Loading
Loading
@@ -632,7 +682,7 @@ func (e *Exporter) scrapeRedisHost(scrapes chan<- scrapeResult, addr string, idx
for _, key := range obtainedKeys {
dbLabel := "db" + k.db
keyLabel := key
if tempVal, err := c.Do("GET", key); err == nil && tempVal != nil {
if tempVal, err := doRedisCmd(c, "GET", key); err == nil && tempVal != nil {
if val, err := strconv.ParseFloat(fmt.Sprintf("%s", tempVal), 64); err == nil {
e.keyValues.WithLabelValues(addr, e.redis.Aliases[idx], dbLabel, keyLabel).Set(val)
}
Loading
Loading
@@ -646,7 +696,7 @@ func (e *Exporter) scrapeRedisHost(scrapes chan<- scrapeResult, addr string, idx
"PFCOUNT",
"STRLEN",
} {
if tempVal, err := c.Do(op, key); err == nil && tempVal != nil {
if tempVal, err := doRedisCmd(c, op, key); err == nil && tempVal != nil {
e.keySizes.WithLabelValues(addr, e.redis.Aliases[idx], dbLabel, keyLabel).Set(float64(tempVal.(int64)))
break
}
Loading
Loading
@@ -654,18 +704,18 @@ func (e *Exporter) scrapeRedisHost(scrapes chan<- scrapeResult, addr string, idx
}
}
 
log.Debugf("scrapeRedisHost() done")
return nil
}
 
func (e *Exporter) scrape(scrapes chan<- scrapeResult) {
defer close(scrapes)
now := time.Now().UnixNano()
e.totalScrapes.Inc()
 
errorCount := 0
for idx, addr := range e.redis.Addrs {
var up float64 = 1
if err := e.scrapeRedisHost(scrapes, addr, idx); err != nil {
errorCount++
Loading
Loading
Loading
Loading
@@ -9,20 +9,23 @@ package exporter
*/
 
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"time"
 
"bytes"
"flag"
"github.com/garyburd/redigo/redis"
"github.com/gomodule/redigo/redis"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
dto "github.com/prometheus/client_model/go"
log "github.com/sirupsen/logrus"
)
 
const (
Loading
Loading
@@ -42,6 +45,8 @@ var (
 
dbNumStr = "11"
dbNumStrFull = fmt.Sprintf("db%s", dbNumStr)
TestServerURL = ""
)
 
const (
Loading
Loading
@@ -96,23 +101,21 @@ func resetLatency(t *testing.T, addr string) error {
return nil
}
 
func getMetrics(t *testing.T) []byte {
resp, err := http.Get("http://127.0.0.1:9121/metrics")
func downloadUrl(t *testing.T, url string) []byte {
log.Debugf("downloadURL() %s", url)
resp, err := http.Get(url)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return body
}
 
func TestLatencySpike(t *testing.T) {
e, _ := NewRedisExporter(defaultRedisHost, "test", "")
 
setupLatency(t, defaultRedisHost.Addrs[0])
Loading
Loading
@@ -224,7 +227,6 @@ func deleteKeysFromDB(t *testing.T, addr string) error {
}
 
c.Do("DEL", TestSetName)
return nil
}
 
Loading
Loading
@@ -568,6 +570,8 @@ func TestCommandStats(t *testing.T) {
}
 
func TestHTTPEndpoint(t *testing.T) {
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()
 
e, _ := NewRedisExporter(defaultRedisHost, "test", dbNumStrFull+"="+url.QueryEscape(keys[0]))
 
Loading
Loading
@@ -575,7 +579,7 @@ func TestHTTPEndpoint(t *testing.T) {
defer deleteKeysFromDB(t, defaultRedisHost.Addrs[0])
prometheus.Register(e)
 
body := getMetrics(t)
body := downloadUrl(t, ts.URL+"/metrics")
 
tests := []string{
// metrics
Loading
Loading
@@ -598,12 +602,10 @@ func TestHTTPEndpoint(t *testing.T) {
}
 
func TestNonExistingHost(t *testing.T) {
rr := RedisHost{Addrs: []string{"unix:///tmp/doesnt.exist"}, Aliases: []string{""}}
e, _ := NewRedisExporter(rr, "test", "")
 
chM := make(chan prometheus.Metric)
go func() {
e.Collect(chM)
close(chM)
Loading
Loading
@@ -752,6 +754,9 @@ func TestSanitizeMetricName(t *testing.T) {
}
 
func TestKeysReset(t *testing.T) {
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()
e, _ := NewRedisExporter(defaultRedisHost, "test", dbNumStrFull+"="+keys[0])
 
setupDBKeys(t, defaultRedisHost.Addrs[0])
Loading
Loading
@@ -759,13 +764,13 @@ func TestKeysReset(t *testing.T) {
 
prometheus.Register(e)
 
chM := make(chan prometheus.Metric)
chM := make(chan prometheus.Metric, 10000)
go func() {
e.Collect(chM)
close(chM)
}()
 
body := getMetrics(t)
body := downloadUrl(t, ts.URL+"/metrics")
 
if !bytes.Contains(body, []byte(keys[0])) {
t.Errorf("Did not found key %q\n%s", keys[0], body)
Loading
Loading
@@ -773,14 +778,182 @@ func TestKeysReset(t *testing.T) {
 
deleteKeysFromDB(t, defaultRedisHost.Addrs[0])
 
body = getMetrics(t)
body = downloadUrl(t, ts.URL+"/metrics")
 
if bytes.Contains(body, []byte(keys[0])) {
t.Errorf("Metric is present in metrics list %q\n%s", keys[0], body)
}
}
 
func TestClusterMaster(t *testing.T) {
if os.Getenv("TEST_REDIS_CLUSTER_MASTER_URI") == "" {
log.Println("TEST_REDIS_CLUSTER_MASTER_URI not set - skipping")
t.SkipNow()
return
}
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()
addr := "redis://" + os.Getenv("TEST_REDIS_CLUSTER_MASTER_URI")
host := RedisHost{Addrs: []string{addr}, Aliases: []string{"master"}}
e, _ := NewRedisExporter(host, "test", "")
setupDBKeys(t, defaultRedisHost.Addrs[0])
defer deleteKeysFromDB(t, defaultRedisHost.Addrs[0])
log.Println("wut")
prometheus.Register(e)
chM := make(chan prometheus.Metric, 10000)
go func() {
e.Collect(chM)
close(chM)
}()
body := downloadUrl(t, ts.URL+"/metrics")
if !bytes.Contains(body, []byte("test_instance_info")) {
t.Errorf("Did not found key %q\n%s", keys[0], body)
}
}
func TestPasswordProtectedInstance(t *testing.T) {
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()
testPwd := "p4$$w0rd"
host := defaultRedisHost
host.Passwords = []string{testPwd}
setupDBKeys(t, host.Addrs[0])
// set password for redis instance
c, err := redis.DialURL(host.Addrs[0])
if err != nil {
t.Errorf("couldn't setup redis, err: %s ", err)
return
}
defer c.Close()
if _, err = c.Do("CONFIG", "SET", "requirepass", testPwd); err != nil {
t.Fatalf("error setting password, err: %s", err)
}
c.Flush()
defer func() {
if _, err = c.Do("auth", testPwd); err != nil {
t.Fatalf("error unsetting password, err: %s", err)
}
if _, err = c.Do("CONFIG", "SET", "requirepass", ""); err != nil {
t.Fatalf("error unsetting password, err: %s", err)
}
deleteKeysFromDB(t, host.Addrs[0])
}()
e, _ := NewRedisExporter(host, "test", "")
prometheus.Register(e)
chM := make(chan prometheus.Metric, 10000)
go func() {
e.Collect(chM)
close(chM)
}()
body := downloadUrl(t, ts.URL+"/metrics")
if !bytes.Contains(body, []byte("test_up")) {
t.Errorf("error")
}
}
func TestPasswordInvalid(t *testing.T) {
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()
testPwd := "p4$$w0rd"
host := defaultRedisHost
host.Passwords = []string{"wrong_password"}
setupDBKeys(t, host.Addrs[0])
// set password for redis instance
c, err := redis.DialURL(host.Addrs[0])
if err != nil {
t.Errorf("couldn't setup redis, err: %s ", err)
return
}
defer c.Close()
if _, err = c.Do("CONFIG", "SET", "requirepass", testPwd); err != nil {
t.Fatalf("error setting password, err: %s", err)
}
c.Flush()
defer func() {
if _, err = c.Do("auth", testPwd); err != nil {
t.Fatalf("error unsetting password, err: %s", err)
}
if _, err = c.Do("CONFIG", "SET", "requirepass", ""); err != nil {
t.Fatalf("error unsetting password, err: %s", err)
}
deleteKeysFromDB(t, host.Addrs[0])
}()
e, _ := NewRedisExporter(host, "test", "")
prometheus.Register(e)
chM := make(chan prometheus.Metric, 10000)
go func() {
e.Collect(chM)
close(chM)
}()
body := downloadUrl(t, ts.URL+"/metrics")
log.Println(string(body))
if !bytes.Contains(body, []byte("test_exporter_last_scrape_error 1")) {
t.Errorf(`error, expected string "test_exporter_last_scrape_error 1" in body`)
}
}
func TestClusterSlave(t *testing.T) {
if os.Getenv("TEST_REDIS_CLUSTER_SLAVE_URI") == "" {
log.Println("TEST_REDIS_CLUSTER_SLAVE_URI not set - skipping")
t.SkipNow()
return
}
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()
addr := "redis://" + os.Getenv("TEST_REDIS_CLUSTER_SLAVE_URI")
host := RedisHost{Addrs: []string{addr}, Aliases: []string{"slave"}}
e, _ := NewRedisExporter(host, "test", "")
setupDBKeys(t, defaultRedisHost.Addrs[0])
defer deleteKeysFromDB(t, defaultRedisHost.Addrs[0])
prometheus.Register(e)
chM := make(chan prometheus.Metric, 10000)
go func() {
e.Collect(chM)
close(chM)
}()
body := downloadUrl(t, ts.URL+"/metrics")
if !bytes.Contains(body, []byte("test_instance_info")) {
t.Errorf("Did not found key %q\n%s", keys[0], body)
}
}
func init() {
ll := strings.ToLower(os.Getenv("LOG_LEVEL"))
if pl, err := log.ParseLevel(ll); err == nil {
log.Printf("Setting log level to: %s", ll)
log.SetLevel(pl)
} else {
log.SetLevel(log.InfoLevel)
}
for _, n := range []string{"john", "paul", "ringo", "george"} {
key := fmt.Sprintf("key_%s_%d", n, ts)
keys = append(keys, key)
Loading
Loading
@@ -807,7 +980,4 @@ func init() {
 
log.Printf("Using redis addrs: %#v", addrs)
defaultRedisHost = RedisHost{Addrs: []string{"redis://" + *redisAddr}, Aliases: aliases}
http.Handle("/metrics", prometheus.Handler())
go http.ListenAndServe("127.0.0.1:9121", nil)
}
Loading
Loading
@@ -2,8 +2,8 @@ package: github.com/oliver006/redis_exporter
import:
- package: github.com/sirupsen/logrus
version: v0.11.0
- package: github.com/garyburd/redigo
version: v1.2.0
- package: github.com/gomodule/redigo
version: v2.0.0
subpackages:
- redis
- package: github.com/prometheus/client_golang
Loading
Loading
package main
 
import (
"encoding/csv"
"flag"
"net/http"
"os"
"runtime"
"strings"
 
"github.com/cloudfoundry-community/go-cfenv"
"github.com/oliver006/redis_exporter/exporter"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
Loading
Loading
@@ -30,9 +27,6 @@ var (
showVersion = flag.Bool("version", false, "Show version information and exit")
useCfBindings = flag.Bool("use-cf-bindings", false, "Use Cloud Foundry service bindings")
redisMetricsOnly = flag.Bool("redis-only-metrics", false, "Whether to export go runtime metrics also")
addrs []string
passwords []string
aliases []string
 
// VERSION, BUILD_DATE, GIT_COMMIT are filled in by the build script
VERSION = "<<< filled in by build >>>"
Loading
Loading
@@ -40,6 +34,13 @@ var (
COMMIT_SHA1 = "<<< filled in by build >>>"
)
 
func getEnv(key string, defaultVal string) string {
if envVal, ok := os.LookupEnv(key); ok {
return envVal
}
return defaultVal
}
func main() {
flag.Parse()
 
Loading
Loading
@@ -68,17 +69,19 @@ func main() {
log.Fatal("Cannot specify both redis.addr and redis.file")
}
 
var addrs, passwords, aliases []string
switch {
case *redisFile != "":
var err error
addrs, passwords, aliases, err = loadRedisFile(*redisFile)
addrs, passwords, aliases, err = exporter.LoadRedisFile(*redisFile)
if err != nil {
log.Fatal(err)
}
case *useCfBindings:
addrs, passwords, aliases = getCloudFoundryRedisBindings()
addrs, passwords, aliases = exporter.GetCloudFoundryRedisBindings()
default:
addrs, passwords, aliases = loadRedisArgs(*redisAddr, *redisPassword, *redisAlias, *separator)
addrs, passwords, aliases = exporter.LoadRedisArgs(*redisAddr, *redisPassword, *redisAlias, *separator)
}
 
exp, err := exporter.NewRedisExporter(
Loading
Loading
@@ -104,7 +107,7 @@ func main() {
} else {
prometheus.MustRegister(exp)
prometheus.MustRegister(buildInfo)
http.Handle(*metricPath, prometheus.Handler())
http.Handle(*metricPath, promhttp.Handler())
}
 
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
Loading
Loading
@@ -124,101 +127,3 @@ func main() {
log.Printf("Using alias: %#v", aliases)
log.Fatal(http.ListenAndServe(*listenAddress, nil))
}
// loadRedisArgs loads the configuration for which redis hosts to monitor from either
// the environment or as passed from program arguments. Returns the list of host addrs,
// passwords, and their aliases.
func loadRedisArgs(addr, password, alias, separator string) ([]string, []string, []string) {
if addr == "" {
addr = "redis://localhost:6379"
}
addrs = strings.Split(addr, separator)
passwords = strings.Split(password, separator)
for len(passwords) < len(addrs) {
passwords = append(passwords, passwords[0])
}
aliases = strings.Split(alias, separator)
for len(aliases) < len(addrs) {
aliases = append(aliases, aliases[0])
}
return addrs, passwords, aliases
}
// loadRedisFile opens the specified file and loads the configuration for which redis
// hosts to monitor. Returns the list of hosts addrs, passwords, and their aliases.
func loadRedisFile(fileName string) ([]string, []string, []string, error) {
var addrs []string
var passwords []string
var aliases []string
file, err := os.Open(fileName)
if err != nil {
return nil, nil, nil, err
}
r := csv.NewReader(file)
r.FieldsPerRecord = -1
records, err := r.ReadAll()
if err != nil {
return nil, nil, nil, err
}
file.Close()
// For each line, test if it contains an optional password and alias and provide them,
// else give them empty strings
for _, record := range records {
length := len(record)
switch length {
case 3:
addrs = append(addrs, record[0])
passwords = append(passwords, record[1])
aliases = append(aliases, record[2])
case 2:
addrs = append(addrs, record[0])
passwords = append(passwords, record[1])
aliases = append(aliases, "")
case 1:
addrs = append(addrs, record[0])
passwords = append(passwords, "")
aliases = append(aliases, "")
}
}
return addrs, passwords, aliases, nil
}
// getEnv gets an environment variable from a given key and if it doesn't exist,
// returns defaultVal given.
func getEnv(key string, defaultVal string) string {
if envVal, ok := os.LookupEnv(key); ok {
return envVal
}
return defaultVal
}
func getCloudFoundryRedisBindings() (addrs, passwords, aliases []string) {
if !cfenv.IsRunningOnCF() {
return
}
appEnv, err := cfenv.Current()
if err != nil {
log.Warnln("Unable to get current CF environment", err)
return
}
redisServices, err := appEnv.Services.WithTag("redis")
if err != nil {
log.Warnln("Error while getting redis services", err)
return
}
for _, redisService := range redisServices {
credentials := redisService.Credentials
addr := credentials["hostname"].(string) + ":" + credentials["port"].(string)
password := credentials["password"].(string)
alias := redisService.Name
addrs = append(addrs, addr)
passwords = append(passwords, password)
aliases = append(aliases, alias)
}
return
}
Ask questions at
[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).
[Open an issue](https://github.com/garyburd/redigo/issues/new) to discuss your
plans before doing any work on Redigo.
Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis
language: go
sudo: false
services:
- redis-server
go:
- 1.4
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...
Redigo
======
[![Build Status](https://travis-ci.org/gomodule/redigo.svg?branch=master)](https://travis-ci.org/gomodule/redigo)
[![GoDoc](https://godoc.org/github.com/gomodule/redigo/redis?status.svg)](https://godoc.org/github.com/gomodule/redigo/redis)
Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database.
Features
-------
* A [Print-like](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.
* [Pipelining](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Pipelining), including pipelined transactions.
* [Publish/Subscribe](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Publish_and_Subscribe).
* [Connection pooling](http://godoc.org/github.com/gomodule/redigo/redis#Pool).
* [Script helper type](http://godoc.org/github.com/gomodule/redigo/redis#Script) with optimistic use of EVALSHA.
* [Helper functions](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Reply_Helpers) for working with command replies.
Documentation
-------------
- [API Reference](http://godoc.org/github.com/gomodule/redigo/redis)
- [FAQ](https://github.com/gomodule/redigo/wiki/FAQ)
- [Examples](https://godoc.org/github.com/gomodule/redigo/redis#pkg-examples)
Installation
------------
Install Redigo using the "go get" command:
go get github.com/gomodule/redigo/redis
The Go distribution is Redigo's only dependency.
Related Projects
----------------
- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo.
- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation.
- [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo
- [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo
Contributing
------------
See [CONTRIBUTING.md](https://github.com/gomodule/redigo/blob/master/.github/CONTRIBUTING.md).
License
-------
Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
Loading
Loading
@@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
 
package internal // import "github.com/garyburd/redigo/internal"
package internal // import "github.com/gomodule/redigo/internal"
 
import (
"strings"
Loading
Loading
package internal
import "testing"
func TestLookupCommandInfo(t *testing.T) {
for _, n := range []string{"watch", "WATCH", "wAtch"} {
if LookupCommandInfo(n) == (CommandInfo{}) {
t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n)
}
}
}
func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
for i := 0; i < b.N; i++ {
for _, c := range names {
LookupCommandInfo(c)
}
}
}
func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR")
}
func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {
benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR")
}