mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
Remove RRD dependency and switch to dynamic linking
This commit is contained in:
1
Makefile
1
Makefile
@@ -30,7 +30,6 @@ endif
|
||||
define LDFLAGS
|
||||
-ldflags "\
|
||||
-s -w \
|
||||
-extldflags \"-static\" \
|
||||
-X ${VERSION_PACKAGE}.version=${VERSION} \
|
||||
-X ${VERSION_PACKAGE}.gitSHA=${GIT_SHA} \
|
||||
-X ${VERSION_PACKAGE}.buildTime=${DATE} \
|
||||
|
||||
17
README.md
17
README.md
@@ -39,20 +39,3 @@ For details on creating the custom resource files that drive support-bundle coll
|
||||
# Community
|
||||
|
||||
For questions about using Troubleshoot, there's a [Replicated Community](https://help.replicated.com/community) forum, and a [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot).
|
||||
|
||||
# Building
|
||||
|
||||
The following packages are required for building the project from source code:
|
||||
|
||||
pkg-config
|
||||
librrd-dev
|
||||
libglib2.0-dev
|
||||
libcairo2-dev
|
||||
libpango1.0-dev
|
||||
libpixman-1-dev
|
||||
libpng-dev
|
||||
libsdl-pango-dev
|
||||
libthai-dev
|
||||
libpcre3-dev
|
||||
|
||||
There are known issues with libc6 2.27-3ubuntu1.2 on Ubuntu 18. Upgrading to 2.27-3ubuntu1.3 (apt-get install libc6) resolves these.
|
||||
@@ -10,7 +10,7 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
main: ffi/main.go
|
||||
flags: -buildmode=c-shared
|
||||
@@ -23,13 +23,12 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CGO_ENABLED=0
|
||||
main: cmd/preflight/main.go
|
||||
ldflags: -s -w
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: preflight
|
||||
hooks: {}
|
||||
@@ -45,7 +44,6 @@ builds:
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: preflight
|
||||
hooks: {}
|
||||
@@ -56,13 +54,12 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CGO_ENABLED=0
|
||||
main: cmd/troubleshoot/main.go
|
||||
ldflags: -s -w
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: support-bundle
|
||||
hooks: {}
|
||||
@@ -78,7 +75,6 @@ builds:
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: support-bundle
|
||||
hooks: {}
|
||||
|
||||
@@ -251,20 +251,6 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.Collectd != nil {
|
||||
isExcluded, err := isExcluded(analyzer.Collectd.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
result, err := analyzeCollectd(analyzer.Collectd, findFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
return nil, errors.New("invalid analyzer")
|
||||
|
||||
}
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/rrd"
|
||||
)
|
||||
|
||||
type CollectdSummary struct {
|
||||
Load float64
|
||||
}
|
||||
|
||||
func analyzeCollectd(analyzer *troubleshootv1beta2.CollectdAnalyze, getCollectedFileContents func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
|
||||
rrdArchives, err := getCollectedFileContents("/collectd/rrd/*.tar")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find rrd archives")
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "rrd")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create temp rrd dir")
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
for name, data := range rrdArchives {
|
||||
destDir := filepath.Join(tmpDir, filepath.Base(name))
|
||||
if err := extractRRDFiles(data, destDir); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to extract rrd file")
|
||||
}
|
||||
}
|
||||
|
||||
loadFiles, err := findRRDLoadFiles(tmpDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find load files")
|
||||
}
|
||||
|
||||
collectdSummary := CollectdSummary{
|
||||
Load: 0,
|
||||
}
|
||||
|
||||
// load files are always present, so this loop can be used for all host metrics
|
||||
for _, loadFile := range loadFiles {
|
||||
pathParts := strings.Split(loadFile, string(filepath.Separator))
|
||||
if len(pathParts) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
// .../<hostname>/load/load.rrd
|
||||
hostname := pathParts[len(pathParts)-3]
|
||||
hostDir := strings.TrimSuffix(loadFile, "/load/load.rrd")
|
||||
|
||||
hostLoad, err := getHostLoad(analyzer, loadFile, hostDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find analyze %s files", hostname)
|
||||
}
|
||||
|
||||
collectdSummary.Load = math.Max(collectdSummary.Load, hostLoad)
|
||||
}
|
||||
|
||||
result, err := getCollectdAnalyzerOutcome(analyzer, collectdSummary)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate outcome")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func extractRRDFiles(archiveData []byte, dst string) error {
|
||||
tarReader := tar.NewReader(bytes.NewReader(archiveData))
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "failed to read rrd archive")
|
||||
}
|
||||
|
||||
if header.Typeflag != tar.TypeReg {
|
||||
continue
|
||||
}
|
||||
|
||||
dstFileName := filepath.Join(dst, header.Name)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dstFileName), 0755); err != nil {
|
||||
return errors.Wrap(err, "failed to create dest path")
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
f, err := os.Create(dstFileName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create dest file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, tarReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to copy")
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write dest file")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findRRDLoadFiles(rootDir string) ([]string, error) {
|
||||
files := make([]string, 0)
|
||||
err := filepath.Walk(rootDir, func(filename string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if filepath.Base(filename) == "load.rrd" {
|
||||
files = append(files, filename)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find rrd load files")
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func getHostLoad(analyzer *troubleshootv1beta2.CollectdAnalyze, loadFile string, hostRoot string) (float64, error) {
|
||||
numberOfCPUs := 0
|
||||
err := filepath.Walk(hostRoot, func(filename string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(filepath.Base(filename), "cpu-") {
|
||||
numberOfCPUs++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to find rrd files")
|
||||
}
|
||||
|
||||
if numberOfCPUs == 0 {
|
||||
numberOfCPUs = 1 // what else can we do here? return an error?
|
||||
}
|
||||
|
||||
fileInfo, err := rrd.Info(loadFile)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to get rrd info")
|
||||
}
|
||||
|
||||
// Query RRD data. Start and end have to be multiples of step.
|
||||
|
||||
window := 7 * 24 * time.Hour
|
||||
step := 1 * time.Hour
|
||||
lastUpdate := int64(fileInfo["last_update"].(uint))
|
||||
endSeconds := int64(lastUpdate/int64(step.Seconds())) * int64(step.Seconds())
|
||||
end := time.Unix(int64(endSeconds), 0)
|
||||
start := end.Add(-window)
|
||||
fetchResult, err := rrd.Fetch(loadFile, "MAX", start, end, step)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to fetch load data")
|
||||
}
|
||||
defer fetchResult.FreeValues()
|
||||
|
||||
values := fetchResult.Values()
|
||||
maxLoad := float64(0)
|
||||
for i := 0; i < len(values); i += 3 { // +3 because "shortterm", "midterm", "longterm"
|
||||
v := values[i+1] // midterm
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
maxLoad = math.Max(maxLoad, values[i+1])
|
||||
}
|
||||
|
||||
return maxLoad / float64(numberOfCPUs), nil
|
||||
}
|
||||
|
||||
func getCollectdAnalyzerOutcome(analyzer *troubleshootv1beta2.CollectdAnalyze, collectdSummary CollectdSummary) (*AnalyzeResult, error) {
|
||||
collectorName := analyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "rrd"
|
||||
}
|
||||
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = collectorName
|
||||
}
|
||||
result := &AnalyzeResult{
|
||||
Title: title,
|
||||
IconKey: "host_load_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rrd-analyze.svg",
|
||||
}
|
||||
|
||||
for _, outcome := range analyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareCollectdConditionalToActual(outcome.Fail.When, collectdSummary)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare rrd fail conditional")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareCollectdConditionalToActual(outcome.Warn.When, collectdSummary)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare rrd warn conditional")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareCollectdConditionalToActual(outcome.Pass.When, collectdSummary)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare rrd pass conditional")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func compareCollectdConditionalToActual(conditional string, collectdSummary CollectdSummary) (bool, error) {
|
||||
parts := strings.Split(strings.TrimSpace(conditional), " ")
|
||||
|
||||
if len(parts) != 3 {
|
||||
return false, errors.New("unable to parse conditional")
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "load":
|
||||
expected, err := strconv.ParseFloat(parts[2], 64)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse float")
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case "=", "==", "===":
|
||||
return collectdSummary.Load == expected, nil
|
||||
case "!=", "!==":
|
||||
return collectdSummary.Load != expected, nil
|
||||
case "<":
|
||||
return collectdSummary.Load < expected, nil
|
||||
|
||||
case ">":
|
||||
return collectdSummary.Load > expected, nil
|
||||
|
||||
case "<=":
|
||||
return collectdSummary.Load <= expected, nil
|
||||
|
||||
case ">=":
|
||||
return collectdSummary.Load >= expected, nil
|
||||
}
|
||||
|
||||
return false, errors.Errorf("unknown rrd comparator: %q", parts[0])
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
@@ -142,5 +142,4 @@ type Analyze struct {
|
||||
Postgres *DatabaseAnalyze `json:"postgres,omitempty" yaml:"postgres,omitempty"`
|
||||
Mysql *DatabaseAnalyze `json:"mysql,omitempty" yaml:"mysql,omitempty"`
|
||||
Redis *DatabaseAnalyze `json:"redis,omitempty" yaml:"redis,omitempty"`
|
||||
Collectd *CollectdAnalyze `json:"collectd,omitempty" yaml:"collectd,omitempty"`
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ func (c *Collect) GetName() string {
|
||||
name = c.HTTP.CollectorName
|
||||
}
|
||||
if c.Collectd != nil {
|
||||
collector = "rrd"
|
||||
collector = "collectd"
|
||||
name = c.Collectd.CollectorName
|
||||
}
|
||||
|
||||
|
||||
@@ -127,11 +127,6 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
|
||||
*out = new(DatabaseAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Collectd != nil {
|
||||
in, out := &in.Collectd, &out.Collectd
|
||||
*out = new(CollectdAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
Copyright (c) 2012, Michal Derkacz
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,18 +0,0 @@
|
||||
# Go bindings to rrdtool C library (rrdtool)
|
||||
|
||||
This package implements [Go](http://golang.org) (golang) bindings for the [rrdtool](http://oss.oetiker.ch/rrdtool/) C API.
|
||||
|
||||
## Installing
|
||||
|
||||
rrd currently supports rrdtool-1.4.x
|
||||
|
||||
Install rrd with:
|
||||
|
||||
go get github.com/ziutek/rrd
|
||||
|
||||
## Usage
|
||||
|
||||
See [GoDoc](http://godoc.org/github.com/ziutek/rrd) for documentation.
|
||||
|
||||
## Example
|
||||
See [rrd_test.go](https://github.com/ziutek/rrd/blob/master/rrd_test.go) for an example of using this package.
|
||||
505
pkg/rrd/rrd.go
505
pkg/rrd/rrd.go
@@ -1,505 +0,0 @@
|
||||
// Copied from "github.com/ziutek/rrd"
|
||||
|
||||
// Simple wrapper for rrdtool C library
|
||||
package rrd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Error string
|
||||
|
||||
func (e Error) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
/*
|
||||
type cstring []byte
|
||||
|
||||
func newCstring(s string) cstring {
|
||||
cs := make(cstring, len(s)+1)
|
||||
copy(cs, s)
|
||||
return cs
|
||||
}
|
||||
|
||||
func (cs cstring) p() unsafe.Pointer {
|
||||
if len(cs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return unsafe.Pointer(&cs[0])
|
||||
}
|
||||
|
||||
func (cs cstring) String() string {
|
||||
return string(cs[:len(cs)-1])
|
||||
}
|
||||
*/
|
||||
|
||||
func join(args []interface{}) string {
|
||||
sa := make([]string, len(args))
|
||||
for i, a := range args {
|
||||
var s string
|
||||
switch v := a.(type) {
|
||||
case time.Time:
|
||||
s = i64toa(v.Unix())
|
||||
default:
|
||||
s = fmt.Sprint(v)
|
||||
}
|
||||
sa[i] = s
|
||||
}
|
||||
return strings.Join(sa, ":")
|
||||
}
|
||||
|
||||
type Creator struct {
|
||||
filename string
|
||||
start time.Time
|
||||
step uint
|
||||
args []string
|
||||
}
|
||||
|
||||
// NewCreator returns new Creator object. You need to call Create to really
|
||||
// create database file.
|
||||
// filename - name of database file
|
||||
// start - don't accept any data timed before or at time specified
|
||||
// step - base interval in seconds with which data will be fed into RRD
|
||||
func NewCreator(filename string, start time.Time, step uint) *Creator {
|
||||
return &Creator{
|
||||
filename: filename,
|
||||
start: start,
|
||||
step: step,
|
||||
}
|
||||
}
|
||||
|
||||
// DS formats a DS argument and appends it to the list of arguments to be
|
||||
// passed to rrdcreate(). Each element of args is formatted with fmt.Sprint().
|
||||
// Please see the rrdcreate(1) manual page for in-depth documentation.
|
||||
func (c *Creator) DS(name, compute string, args ...interface{}) {
|
||||
c.args = append(c.args, "DS:"+name+":"+compute+":"+join(args))
|
||||
}
|
||||
|
||||
// RRA formats an RRA argument and appends it to the list of arguments to be
|
||||
// passed to rrdcreate(). Each element of args is formatted with fmt.Sprint().
|
||||
// Please see the rrdcreate(1) manual page for in-depth documentation.
|
||||
func (c *Creator) RRA(cf string, args ...interface{}) {
|
||||
c.args = append(c.args, "RRA:"+cf+":"+join(args))
|
||||
}
|
||||
|
||||
// Create creates new database file. If overwrite is true it overwrites
|
||||
// database file if exists. If overwrite is false it returns error if file
|
||||
// exists (you can use os.IsExist function to check this case).
|
||||
func (c *Creator) Create(overwrite bool) error {
|
||||
if !overwrite {
|
||||
f, err := os.OpenFile(
|
||||
c.filename,
|
||||
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
||||
0666,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
return c.create()
|
||||
}
|
||||
|
||||
// Use cstring and unsafe.Pointer to avoid allocations for C calls
|
||||
|
||||
type Updater struct {
|
||||
filename *cstring
|
||||
template *cstring
|
||||
|
||||
args []*cstring
|
||||
}
|
||||
|
||||
func NewUpdater(filename string) *Updater {
|
||||
u := &Updater{filename: newCstring(filename)}
|
||||
runtime.SetFinalizer(u, cfree)
|
||||
return u
|
||||
}
|
||||
|
||||
func cfree(u *Updater) {
|
||||
u.filename.Free()
|
||||
u.template.Free()
|
||||
for _, a := range u.args {
|
||||
a.Free()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) SetTemplate(dsName ...string) {
|
||||
u.template.Free()
|
||||
u.template = newCstring(strings.Join(dsName, ":"))
|
||||
}
|
||||
|
||||
// Cache chaches data for later save using Update(). Use it to avoid
|
||||
// open/read/write/close for every update.
|
||||
func (u *Updater) Cache(args ...interface{}) {
|
||||
u.args = append(u.args, newCstring(join(args)))
|
||||
}
|
||||
|
||||
// Update saves data in RRDB.
|
||||
// Without args Update saves all subsequent updates buffered by Cache method.
|
||||
// If you specify args it saves them immediately.
|
||||
func (u *Updater) Update(args ...interface{}) error {
|
||||
if len(args) != 0 {
|
||||
cs := newCstring(join(args))
|
||||
err := u.update([]*cstring{cs})
|
||||
cs.Free()
|
||||
return err
|
||||
} else if len(u.args) != 0 {
|
||||
err := u.update(u.args)
|
||||
for _, a := range u.args {
|
||||
a.Free()
|
||||
}
|
||||
u.args = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GraphInfo struct {
|
||||
Print []string
|
||||
Width, Height uint
|
||||
Ymin, Ymax float64
|
||||
}
|
||||
|
||||
type Grapher struct {
|
||||
title string
|
||||
vlabel string
|
||||
width, height uint
|
||||
borderWidth uint
|
||||
upperLimit float64
|
||||
lowerLimit float64
|
||||
rigid bool
|
||||
altAutoscale bool
|
||||
altAutoscaleMin bool
|
||||
altAutoscaleMax bool
|
||||
noGridFit bool
|
||||
|
||||
logarithmic bool
|
||||
unitsExponent int
|
||||
unitsLength uint
|
||||
|
||||
rightAxisScale float64
|
||||
rightAxisShift float64
|
||||
rightAxisLabel string
|
||||
|
||||
noLegend bool
|
||||
|
||||
lazy bool
|
||||
|
||||
colors map[string]string
|
||||
|
||||
slopeMode bool
|
||||
|
||||
watermark string
|
||||
base uint
|
||||
imageFormat string
|
||||
interlaced bool
|
||||
|
||||
daemon string
|
||||
|
||||
args []string
|
||||
}
|
||||
|
||||
const (
|
||||
maxUint = ^uint(0)
|
||||
maxInt = int(maxUint >> 1)
|
||||
minInt = -maxInt - 1
|
||||
defWidth = 2
|
||||
)
|
||||
|
||||
func NewGrapher() *Grapher {
|
||||
return &Grapher{
|
||||
upperLimit: -math.MaxFloat64,
|
||||
lowerLimit: math.MaxFloat64,
|
||||
unitsExponent: minInt,
|
||||
borderWidth: defWidth,
|
||||
colors: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Grapher) SetTitle(title string) {
|
||||
g.title = title
|
||||
}
|
||||
|
||||
func (g *Grapher) SetVLabel(vlabel string) {
|
||||
g.vlabel = vlabel
|
||||
}
|
||||
|
||||
func (g *Grapher) SetSize(width, height uint) {
|
||||
g.width = width
|
||||
g.height = height
|
||||
}
|
||||
|
||||
func (g *Grapher) SetBorder(width uint) {
|
||||
g.borderWidth = width
|
||||
}
|
||||
|
||||
func (g *Grapher) SetLowerLimit(limit float64) {
|
||||
g.lowerLimit = limit
|
||||
}
|
||||
|
||||
func (g *Grapher) SetUpperLimit(limit float64) {
|
||||
g.upperLimit = limit
|
||||
}
|
||||
|
||||
func (g *Grapher) SetRigid() {
|
||||
g.rigid = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetAltAutoscale() {
|
||||
g.altAutoscale = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetAltAutoscaleMin() {
|
||||
g.altAutoscaleMin = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetAltAutoscaleMax() {
|
||||
|
||||
g.altAutoscaleMax = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetNoGridFit() {
|
||||
g.noGridFit = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetLogarithmic() {
|
||||
g.logarithmic = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetUnitsExponent(e int) {
|
||||
g.unitsExponent = e
|
||||
}
|
||||
|
||||
func (g *Grapher) SetUnitsLength(l uint) {
|
||||
g.unitsLength = l
|
||||
}
|
||||
|
||||
func (g *Grapher) SetRightAxis(scale, shift float64) {
|
||||
g.rightAxisScale = scale
|
||||
g.rightAxisShift = shift
|
||||
}
|
||||
|
||||
func (g *Grapher) SetRightAxisLabel(label string) {
|
||||
g.rightAxisLabel = label
|
||||
}
|
||||
|
||||
func (g *Grapher) SetNoLegend() {
|
||||
g.noLegend = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetLazy() {
|
||||
g.lazy = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetColor(colortag, color string) {
|
||||
g.colors[colortag] = color
|
||||
}
|
||||
|
||||
func (g *Grapher) SetSlopeMode() {
|
||||
g.slopeMode = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetImageFormat(format string) {
|
||||
g.imageFormat = format
|
||||
}
|
||||
|
||||
func (g *Grapher) SetInterlaced() {
|
||||
g.interlaced = true
|
||||
}
|
||||
|
||||
func (g *Grapher) SetBase(base uint) {
|
||||
g.base = base
|
||||
}
|
||||
|
||||
func (g *Grapher) SetWatermark(watermark string) {
|
||||
g.watermark = watermark
|
||||
}
|
||||
|
||||
func (g *Grapher) SetDaemon(daemon string) {
|
||||
g.daemon = daemon
|
||||
}
|
||||
|
||||
func (g *Grapher) AddOptions(options ...string) {
|
||||
g.args = append(g.args, options...)
|
||||
}
|
||||
|
||||
func (g *Grapher) push(cmd string, options []string) {
|
||||
if len(options) > 0 {
|
||||
cmd += ":" + strings.Join(options, ":")
|
||||
}
|
||||
g.args = append(g.args, cmd)
|
||||
}
|
||||
|
||||
func (g *Grapher) Def(vname, rrdfile, dsname, cf string, options ...string) {
|
||||
g.push(
|
||||
fmt.Sprintf("DEF:%s=%s:%s:%s", vname, rrdfile, dsname, cf),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Grapher) VDef(vname, rpn string) {
|
||||
g.push("VDEF:"+vname+"="+rpn, nil)
|
||||
}
|
||||
|
||||
func (g *Grapher) CDef(vname, rpn string) {
|
||||
g.push("CDEF:"+vname+"="+rpn, nil)
|
||||
}
|
||||
|
||||
func (g *Grapher) Print(vname, format string) {
|
||||
g.push("PRINT:"+vname+":"+format, nil)
|
||||
}
|
||||
|
||||
func (g *Grapher) PrintT(vname, format string) {
|
||||
g.push("PRINT:"+vname+":"+format+":strftime", nil)
|
||||
}
|
||||
func (g *Grapher) GPrint(vname, format string) {
|
||||
g.push("GPRINT:"+vname+":"+format, nil)
|
||||
}
|
||||
|
||||
func (g *Grapher) GPrintT(vname, format string) {
|
||||
g.push("GPRINT:"+vname+":"+format+":strftime", nil)
|
||||
}
|
||||
|
||||
func (g *Grapher) Comment(s string) {
|
||||
g.push("COMMENT:"+s, nil)
|
||||
}
|
||||
|
||||
func (g *Grapher) VRule(t interface{}, color string, options ...string) {
|
||||
if v, ok := t.(time.Time); ok {
|
||||
t = v.Unix()
|
||||
}
|
||||
vr := fmt.Sprintf("VRULE:%v#%s", t, color)
|
||||
g.push(vr, options)
|
||||
}
|
||||
|
||||
func (g *Grapher) HRule(value, color string, options ...string) {
|
||||
hr := "HRULE:" + value + "#" + color
|
||||
g.push(hr, options)
|
||||
}
|
||||
|
||||
func (g *Grapher) Line(width float32, value, color string, options ...string) {
|
||||
line := fmt.Sprintf("LINE%f:%s", width, value)
|
||||
if color != "" {
|
||||
line += "#" + color
|
||||
}
|
||||
g.push(line, options)
|
||||
}
|
||||
|
||||
func (g *Grapher) Area(value, color string, options ...string) {
|
||||
area := "AREA:" + value
|
||||
if color != "" {
|
||||
area += "#" + color
|
||||
}
|
||||
g.push(area, options)
|
||||
}
|
||||
|
||||
func (g *Grapher) Tick(vname, color string, options ...string) {
|
||||
tick := "TICK:" + vname
|
||||
if color != "" {
|
||||
tick += "#" + color
|
||||
}
|
||||
g.push(tick, options)
|
||||
}
|
||||
|
||||
func (g *Grapher) Shift(vname string, offset interface{}) {
|
||||
if v, ok := offset.(time.Duration); ok {
|
||||
offset = int64((v + time.Second/2) / time.Second)
|
||||
}
|
||||
shift := fmt.Sprintf("SHIFT:%s:%v", vname, offset)
|
||||
g.push(shift, nil)
|
||||
}
|
||||
|
||||
func (g *Grapher) TextAlign(align string) {
|
||||
g.push("TEXTALIGN:"+align, nil)
|
||||
}
|
||||
|
||||
// Graph returns GraphInfo and image as []byte or error
|
||||
func (g *Grapher) Graph(start, end time.Time) (GraphInfo, []byte, error) {
|
||||
return g.graph("-", start, end)
|
||||
}
|
||||
|
||||
// SaveGraph saves image to file and returns GraphInfo or error
|
||||
func (g *Grapher) SaveGraph(filename string, start, end time.Time) (GraphInfo, error) {
|
||||
gi, _, err := g.graph(filename, start, end)
|
||||
return gi, err
|
||||
}
|
||||
|
||||
type FetchResult struct {
|
||||
Filename string
|
||||
Cf string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Step time.Duration
|
||||
DsNames []string
|
||||
RowCnt int
|
||||
values []float64
|
||||
}
|
||||
|
||||
func (r *FetchResult) ValueAt(dsIndex, rowIndex int) float64 {
|
||||
return r.values[len(r.DsNames)*rowIndex+dsIndex]
|
||||
}
|
||||
|
||||
type Exporter struct {
|
||||
maxRows uint
|
||||
|
||||
daemon string
|
||||
|
||||
args []string
|
||||
}
|
||||
|
||||
func NewExporter() *Exporter {
|
||||
return &Exporter{}
|
||||
}
|
||||
|
||||
func (e *Exporter) SetMaxRows(maxRows uint) {
|
||||
e.maxRows = maxRows
|
||||
}
|
||||
|
||||
func (e *Exporter) push(cmd string, options []string) {
|
||||
if len(options) > 0 {
|
||||
cmd += ":" + strings.Join(options, ":")
|
||||
}
|
||||
e.args = append(e.args, cmd)
|
||||
}
|
||||
|
||||
func (e *Exporter) Def(vname, rrdfile, dsname, cf string, options ...string) {
|
||||
e.push(
|
||||
fmt.Sprintf("DEF:%s=%s:%s:%s", vname, rrdfile, dsname, cf),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
func (e *Exporter) CDef(vname, rpn string) {
|
||||
e.push("CDEF:"+vname+"="+rpn, nil)
|
||||
}
|
||||
|
||||
func (e *Exporter) XportDef(vname, label string) {
|
||||
e.push("XPORT:"+vname+":"+label, nil)
|
||||
}
|
||||
|
||||
func (e *Exporter) Xport(start, end time.Time, step time.Duration) (XportResult, error) {
|
||||
return e.xport(start, end, step)
|
||||
}
|
||||
|
||||
func (e *Exporter) SetDaemon(daemon string) {
|
||||
e.daemon = daemon
|
||||
}
|
||||
|
||||
type XportResult struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Step time.Duration
|
||||
Legends []string
|
||||
RowCnt int
|
||||
values []float64
|
||||
}
|
||||
|
||||
func (r *XportResult) ValueAt(legendIndex, rowIndex int) float64 {
|
||||
return r.values[len(r.Legends)*rowIndex+legendIndex]
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package rrd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type cstring byte
|
||||
|
||||
func newCstring(s string) *cstring {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *cstring) Free() {
|
||||
return
|
||||
}
|
||||
|
||||
func (cs *cstring) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Creator) create() error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (u *Updater) update(args []*cstring) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func ftoa(f float64) string {
|
||||
return strconv.FormatFloat(f, 'e', 10, 64)
|
||||
}
|
||||
|
||||
func i64toa(i int64) string {
|
||||
return strconv.FormatInt(i, 10)
|
||||
}
|
||||
|
||||
func u64toa(u uint64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func itoa(i int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func utoa(u uint) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseInfoKey(ik string) (kname, kkey string, kid int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Grapher) graph(filename string, start, end time.Time) (GraphInfo, []byte, error) {
|
||||
return GraphInfo{}, nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// Info returns information about RRD file.
|
||||
func Info(filename string) (map[string]interface{}, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// Fetch retrieves data from RRD file.
|
||||
func Fetch(filename, cf string, start, end time.Time, step time.Duration) (FetchResult, error) {
|
||||
return FetchResult{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// FreeValues free values memory allocated by C.
|
||||
func (r *FetchResult) FreeValues() {
|
||||
}
|
||||
|
||||
// Values returns copy of internal array of values.
|
||||
func (r *FetchResult) Values() []float64 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Export data from RRD file(s)
|
||||
func (e *Exporter) xport(start, end time.Time, step time.Duration) (XportResult, error) {
|
||||
return XportResult{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// FreeValues free values memory allocated by C.
|
||||
func (r *XportResult) FreeValues() {
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
package rrd
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <rrd.h>
|
||||
#include "rrdfunc.h"
|
||||
#cgo LDFLAGS: -lpthread -lrrd -lpng -lcairo -lpixman-1 -lpango-1.0 -lfontconfig -lssl -lexpat -lfreetype -lgobject-2.0 -lglib-2.0 -lpcre -lthai -ldatrie -lz -lffi -ldbi -ldl -lc -lm -Wl,--unresolved-symbols=ignore-all
|
||||
#cgo CFLAGS: -std=c99 -Wno-implicit-function-declaration -Wno-int-conversion
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type cstring C.char
|
||||
|
||||
func newCstring(s string) *cstring {
|
||||
cs := C.malloc(C.size_t(len(s) + 1))
|
||||
buf := (*[1<<31 - 1]byte)(cs)[:len(s)+1]
|
||||
copy(buf, s)
|
||||
buf[len(s)] = 0
|
||||
return (*cstring)(cs)
|
||||
}
|
||||
|
||||
func (cs *cstring) Free() {
|
||||
if cs != nil {
|
||||
C.free(unsafe.Pointer(cs))
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *cstring) String() string {
|
||||
buf := (*[1<<31 - 1]byte)(unsafe.Pointer(cs))
|
||||
for n, b := range buf {
|
||||
if b == 0 {
|
||||
return string(buf[:n])
|
||||
}
|
||||
}
|
||||
panic("rrd: bad C string")
|
||||
}
|
||||
|
||||
var mutex sync.Mutex
|
||||
|
||||
func makeArgs(args []string) []*C.char {
|
||||
ret := make([]*C.char, len(args))
|
||||
for i, s := range args {
|
||||
ret[i] = C.CString(s)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func freeCString(s *C.char) {
|
||||
C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func freeArgs(cArgs []*C.char) {
|
||||
for _, s := range cArgs {
|
||||
freeCString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func makeError(e *C.char) error {
|
||||
var null *C.char
|
||||
if e == null {
|
||||
return nil
|
||||
}
|
||||
defer freeCString(e)
|
||||
return Error(C.GoString(e))
|
||||
}
|
||||
|
||||
func (c *Creator) create() error {
|
||||
filename := C.CString(c.filename)
|
||||
defer freeCString(filename)
|
||||
args := makeArgs(c.args)
|
||||
defer freeArgs(args)
|
||||
|
||||
e := C.rrdCreate(
|
||||
filename,
|
||||
C.ulong(c.step),
|
||||
C.time_t(c.start.Unix()),
|
||||
C.int(len(args)),
|
||||
&args[0],
|
||||
)
|
||||
return makeError(e)
|
||||
}
|
||||
|
||||
func (u *Updater) update(args []*cstring) error {
|
||||
e := C.rrdUpdate(
|
||||
(*C.char)(u.filename),
|
||||
(*C.char)(u.template),
|
||||
C.int(len(args)),
|
||||
(**C.char)(unsafe.Pointer(&args[0])),
|
||||
)
|
||||
return makeError(e)
|
||||
}
|
||||
|
||||
var (
|
||||
graphv = C.CString("graphv")
|
||||
xport = C.CString("xport")
|
||||
|
||||
oStart = C.CString("-s")
|
||||
oEnd = C.CString("-e")
|
||||
oTitle = C.CString("-t")
|
||||
oVlabel = C.CString("-v")
|
||||
oWidth = C.CString("-w")
|
||||
oHeight = C.CString("-h")
|
||||
oUpperLimit = C.CString("-u")
|
||||
oLowerLimit = C.CString("-l")
|
||||
oRigid = C.CString("-r")
|
||||
oAltAutoscale = C.CString("-A")
|
||||
oAltAutoscaleMin = C.CString("-J")
|
||||
oAltAutoscaleMax = C.CString("-M")
|
||||
oNoGridFit = C.CString("-N")
|
||||
|
||||
oLogarithmic = C.CString("-o")
|
||||
oUnitsExponent = C.CString("-X")
|
||||
oUnitsLength = C.CString("-L")
|
||||
|
||||
oRightAxis = C.CString("--right-axis")
|
||||
oRightAxisLabel = C.CString("--right-axis-label")
|
||||
|
||||
oDaemon = C.CString("--daemon")
|
||||
|
||||
oBorder = C.CString("--border")
|
||||
|
||||
oNoLegend = C.CString("-g")
|
||||
|
||||
oLazy = C.CString("-z")
|
||||
|
||||
oColor = C.CString("-c")
|
||||
|
||||
oSlopeMode = C.CString("-E")
|
||||
oImageFormat = C.CString("-a")
|
||||
oInterlaced = C.CString("-i")
|
||||
|
||||
oBase = C.CString("-b")
|
||||
oWatermark = C.CString("-W")
|
||||
|
||||
oStep = C.CString("--step")
|
||||
oMaxRows = C.CString("-m")
|
||||
)
|
||||
|
||||
func ftoa(f float64) string {
|
||||
return strconv.FormatFloat(f, 'e', 10, 64)
|
||||
}
|
||||
|
||||
func ftoc(f float64) *C.char {
|
||||
return C.CString(ftoa(f))
|
||||
}
|
||||
|
||||
func i64toa(i int64) string {
|
||||
return strconv.FormatInt(i, 10)
|
||||
}
|
||||
|
||||
func i64toc(i int64) *C.char {
|
||||
return C.CString(i64toa(i))
|
||||
}
|
||||
|
||||
func u64toa(u uint64) string {
|
||||
return strconv.FormatUint(u, 10)
|
||||
}
|
||||
|
||||
func u64toc(u uint64) *C.char {
|
||||
return C.CString(u64toa(u))
|
||||
}
|
||||
func itoa(i int) string {
|
||||
return i64toa(int64(i))
|
||||
}
|
||||
|
||||
func itoc(i int) *C.char {
|
||||
return i64toc(int64(i))
|
||||
}
|
||||
|
||||
func utoa(u uint) string {
|
||||
return u64toa(uint64(u))
|
||||
}
|
||||
|
||||
func utoc(u uint) *C.char {
|
||||
return u64toc(uint64(u))
|
||||
}
|
||||
|
||||
func (g *Grapher) makeArgs(filename string, start, end time.Time) []*C.char {
|
||||
args := []*C.char{
|
||||
graphv, C.CString(filename),
|
||||
oStart, i64toc(start.Unix()),
|
||||
oEnd, i64toc(end.Unix()),
|
||||
oTitle, C.CString(g.title),
|
||||
oVlabel, C.CString(g.vlabel),
|
||||
}
|
||||
if g.width != 0 {
|
||||
args = append(args, oWidth, utoc(g.width))
|
||||
}
|
||||
if g.height != 0 {
|
||||
args = append(args, oHeight, utoc(g.height))
|
||||
}
|
||||
if g.upperLimit != -math.MaxFloat64 {
|
||||
args = append(args, oUpperLimit, ftoc(g.upperLimit))
|
||||
}
|
||||
if g.lowerLimit != math.MaxFloat64 {
|
||||
args = append(args, oLowerLimit, ftoc(g.lowerLimit))
|
||||
}
|
||||
if g.rigid {
|
||||
args = append(args, oRigid)
|
||||
}
|
||||
if g.altAutoscale {
|
||||
args = append(args, oAltAutoscale)
|
||||
}
|
||||
if g.altAutoscaleMax {
|
||||
args = append(args, oAltAutoscaleMax)
|
||||
}
|
||||
if g.altAutoscaleMin {
|
||||
args = append(args, oAltAutoscaleMin)
|
||||
}
|
||||
if g.noGridFit {
|
||||
args = append(args, oNoGridFit)
|
||||
}
|
||||
if g.logarithmic {
|
||||
args = append(args, oLogarithmic)
|
||||
}
|
||||
if g.unitsExponent != minInt {
|
||||
args = append(
|
||||
args,
|
||||
oUnitsExponent, itoc(g.unitsExponent),
|
||||
)
|
||||
}
|
||||
if g.unitsLength != 0 {
|
||||
args = append(
|
||||
args,
|
||||
oUnitsLength, utoc(g.unitsLength),
|
||||
)
|
||||
}
|
||||
if g.rightAxisScale != 0 {
|
||||
args = append(
|
||||
args,
|
||||
oRightAxis,
|
||||
C.CString(ftoa(g.rightAxisScale)+":"+ftoa(g.rightAxisShift)),
|
||||
)
|
||||
}
|
||||
if g.rightAxisLabel != "" {
|
||||
args = append(
|
||||
args,
|
||||
oRightAxisLabel, C.CString(g.rightAxisLabel),
|
||||
)
|
||||
}
|
||||
if g.noLegend {
|
||||
args = append(args, oNoLegend)
|
||||
}
|
||||
if g.lazy {
|
||||
args = append(args, oLazy)
|
||||
}
|
||||
for tag, color := range g.colors {
|
||||
args = append(args, oColor, C.CString(tag+"#"+color))
|
||||
}
|
||||
if g.slopeMode {
|
||||
args = append(args, oSlopeMode)
|
||||
}
|
||||
if g.imageFormat != "" {
|
||||
args = append(args, oImageFormat, C.CString(g.imageFormat))
|
||||
}
|
||||
if g.interlaced {
|
||||
args = append(args, oInterlaced)
|
||||
}
|
||||
if g.base != 0 {
|
||||
args = append(args, oBase, utoc(g.base))
|
||||
}
|
||||
if g.watermark != "" {
|
||||
args = append(args, oWatermark, C.CString(g.watermark))
|
||||
}
|
||||
if g.daemon != "" {
|
||||
args = append(args, oDaemon, C.CString(g.daemon))
|
||||
}
|
||||
if g.borderWidth != defWidth {
|
||||
args = append(args, oBorder, utoc(g.borderWidth))
|
||||
}
|
||||
return append(args, makeArgs(g.args)...)
|
||||
}
|
||||
|
||||
func (e *Exporter) makeArgs(start, end time.Time, step time.Duration) []*C.char {
|
||||
args := []*C.char{
|
||||
xport,
|
||||
oStart, i64toc(start.Unix()),
|
||||
oEnd, i64toc(end.Unix()),
|
||||
oStep, i64toc(int64(step.Seconds())),
|
||||
}
|
||||
if e.maxRows != 0 {
|
||||
args = append(args, oMaxRows, utoc(e.maxRows))
|
||||
}
|
||||
if e.daemon != "" {
|
||||
args = append(args, oDaemon, C.CString(e.daemon))
|
||||
}
|
||||
return append(args, makeArgs(e.args)...)
|
||||
}
|
||||
|
||||
func parseInfoKey(ik string) (kname, kkey string, kid int) {
|
||||
kid = -1
|
||||
o := strings.IndexRune(ik, '[')
|
||||
if o == -1 {
|
||||
kname = ik
|
||||
return
|
||||
}
|
||||
c := strings.IndexRune(ik[o+1:], ']')
|
||||
if c == -1 {
|
||||
kname = ik
|
||||
return
|
||||
}
|
||||
c += o + 1
|
||||
kname = ik[:o] + ik[c+1:]
|
||||
kkey = ik[o+1 : c]
|
||||
if strings.HasPrefix(kname, "ds.") {
|
||||
return
|
||||
} else if id, err := strconv.Atoi(kkey); err == nil && id >= 0 {
|
||||
kid = id
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func updateInfoValue(i *C.struct_rrd_info_t, v interface{}) interface{} {
|
||||
switch i._type {
|
||||
case C.RD_I_VAL:
|
||||
return float64(*(*C.rrd_value_t)(unsafe.Pointer(&i.value[0])))
|
||||
case C.RD_I_CNT:
|
||||
return uint(*(*C.ulong)(unsafe.Pointer(&i.value[0])))
|
||||
case C.RD_I_STR:
|
||||
return C.GoString(*(**C.char)(unsafe.Pointer(&i.value[0])))
|
||||
case C.RD_I_INT:
|
||||
return int(*(*C.int)(unsafe.Pointer(&i.value[0])))
|
||||
case C.RD_I_BLO:
|
||||
blob := *(*C.rrd_blob_t)(unsafe.Pointer(&i.value[0]))
|
||||
b := C.GoBytes(unsafe.Pointer(blob.ptr), C.int(blob.size))
|
||||
if v == nil {
|
||||
return b
|
||||
}
|
||||
return append(v.([]byte), b...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRRDInfo(i *C.rrd_info_t) map[string]interface{} {
|
||||
defer C.rrd_info_free(i)
|
||||
|
||||
r := make(map[string]interface{})
|
||||
for w := (*C.struct_rrd_info_t)(i); w != nil; w = w.next {
|
||||
kname, kkey, kid := parseInfoKey(C.GoString(w.key))
|
||||
v, ok := r[kname]
|
||||
switch {
|
||||
case kid != -1:
|
||||
var a []interface{}
|
||||
if ok {
|
||||
a = v.([]interface{})
|
||||
}
|
||||
if len(a) < kid+1 {
|
||||
oldA := a
|
||||
a = make([]interface{}, kid+1)
|
||||
copy(a, oldA)
|
||||
}
|
||||
a[kid] = updateInfoValue(w, a[kid])
|
||||
v = a
|
||||
case kkey != "":
|
||||
var m map[string]interface{}
|
||||
if ok {
|
||||
m = v.(map[string]interface{})
|
||||
} else {
|
||||
m = make(map[string]interface{})
|
||||
}
|
||||
old, _ := m[kkey]
|
||||
m[kkey] = updateInfoValue(w, old)
|
||||
v = m
|
||||
default:
|
||||
v = updateInfoValue(w, v)
|
||||
}
|
||||
r[kname] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func parseGraphInfo(i *C.rrd_info_t) (gi GraphInfo, img []byte) {
|
||||
inf := parseRRDInfo(i)
|
||||
if v, ok := inf["image_info"]; ok {
|
||||
gi.Print = append(gi.Print, v.(string))
|
||||
}
|
||||
for k, v := range inf {
|
||||
if k == "print" {
|
||||
for _, line := range v.([]interface{}) {
|
||||
gi.Print = append(gi.Print, line.(string))
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := inf["image_width"]; ok {
|
||||
gi.Width = v.(uint)
|
||||
}
|
||||
if v, ok := inf["image_height"]; ok {
|
||||
gi.Height = v.(uint)
|
||||
}
|
||||
if v, ok := inf["value_min"]; ok {
|
||||
gi.Ymin = v.(float64)
|
||||
}
|
||||
if v, ok := inf["value_max"]; ok {
|
||||
gi.Ymax = v.(float64)
|
||||
}
|
||||
if v, ok := inf["image"]; ok {
|
||||
img = v.([]byte)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Grapher) graph(filename string, start, end time.Time) (GraphInfo, []byte, error) {
|
||||
var i *C.rrd_info_t
|
||||
args := g.makeArgs(filename, start, end)
|
||||
|
||||
mutex.Lock() // rrd_graph_v isn't thread safe
|
||||
defer mutex.Unlock()
|
||||
|
||||
err := makeError(C.rrdGraph(
|
||||
&i,
|
||||
C.int(len(args)),
|
||||
&args[0],
|
||||
))
|
||||
|
||||
if err != nil {
|
||||
return GraphInfo{}, nil, err
|
||||
}
|
||||
gi, img := parseGraphInfo(i)
|
||||
|
||||
return gi, img, nil
|
||||
}
|
||||
|
||||
// Info returns information about RRD file.
|
||||
func Info(filename string) (map[string]interface{}, error) {
|
||||
fn := C.CString(filename)
|
||||
defer freeCString(fn)
|
||||
var i *C.rrd_info_t
|
||||
err := makeError(C.rrdInfo(&i, fn))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseRRDInfo(i), nil
|
||||
}
|
||||
|
||||
// Fetch retrieves data from RRD file.
|
||||
func Fetch(filename, cf string, start, end time.Time, step time.Duration) (FetchResult, error) {
|
||||
fn := C.CString(filename)
|
||||
defer freeCString(fn)
|
||||
cCf := C.CString(cf)
|
||||
defer freeCString(cCf)
|
||||
cStart := C.time_t(start.Unix())
|
||||
cEnd := C.time_t(end.Unix())
|
||||
cStep := C.ulong(step.Seconds())
|
||||
var (
|
||||
ret C.int
|
||||
cDsCnt C.ulong
|
||||
cDsNames **C.char
|
||||
cData *C.double
|
||||
)
|
||||
err := makeError(C.rrdFetch(&ret, fn, cCf, &cStart, &cEnd, &cStep, &cDsCnt, &cDsNames, &cData))
|
||||
if err != nil {
|
||||
return FetchResult{filename, cf, start, end, step, nil, 0, nil}, err
|
||||
}
|
||||
|
||||
start = time.Unix(int64(cStart), 0)
|
||||
end = time.Unix(int64(cEnd), 0)
|
||||
step = time.Duration(cStep) * time.Second
|
||||
dsCnt := int(cDsCnt)
|
||||
|
||||
dsNames := make([]string, dsCnt)
|
||||
for i := 0; i < dsCnt; i++ {
|
||||
dsName := C.arrayGetCString(cDsNames, C.int(i))
|
||||
dsNames[i] = C.GoString(dsName)
|
||||
C.free(unsafe.Pointer(dsName))
|
||||
}
|
||||
C.free(unsafe.Pointer(cDsNames))
|
||||
|
||||
rowCnt := (int(cEnd)-int(cStart))/int(cStep) + 1
|
||||
valuesLen := dsCnt * rowCnt
|
||||
var values []float64
|
||||
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&values)))
|
||||
sliceHeader.Cap = valuesLen
|
||||
sliceHeader.Len = valuesLen
|
||||
sliceHeader.Data = uintptr(unsafe.Pointer(cData))
|
||||
return FetchResult{filename, cf, start, end, step, dsNames, rowCnt, values}, nil
|
||||
}
|
||||
|
||||
// FreeValues free values memory allocated by C.
|
||||
func (r *FetchResult) FreeValues() {
|
||||
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&r.values)))
|
||||
C.free(unsafe.Pointer(sliceHeader.Data))
|
||||
}
|
||||
|
||||
// Values returns copy of internal array of values.
|
||||
func (r *FetchResult) Values() []float64 {
|
||||
return append([]float64{}, r.values...)
|
||||
}
|
||||
|
||||
// Export data from RRD file(s)
|
||||
func (e *Exporter) xport(start, end time.Time, step time.Duration) (XportResult, error) {
|
||||
cStart := C.time_t(start.Unix())
|
||||
cEnd := C.time_t(end.Unix())
|
||||
cStep := C.ulong(step.Seconds())
|
||||
args := e.makeArgs(start, end, step)
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
var (
|
||||
ret C.int
|
||||
cXSize C.int
|
||||
cColCnt C.ulong
|
||||
cLegends **C.char
|
||||
cData *C.double
|
||||
)
|
||||
err := makeError(C.rrdXport(
|
||||
&ret,
|
||||
C.int(len(args)),
|
||||
&args[0],
|
||||
&cXSize, &cStart, &cEnd, &cStep, &cColCnt, &cLegends, &cData,
|
||||
))
|
||||
if err != nil {
|
||||
return XportResult{start, end, step, nil, 0, nil}, err
|
||||
}
|
||||
|
||||
start = time.Unix(int64(cStart), 0)
|
||||
end = time.Unix(int64(cEnd), 0)
|
||||
step = time.Duration(cStep) * time.Second
|
||||
colCnt := int(cColCnt)
|
||||
|
||||
legends := make([]string, colCnt)
|
||||
for i := 0; i < colCnt; i++ {
|
||||
legend := C.arrayGetCString(cLegends, C.int(i))
|
||||
legends[i] = C.GoString(legend)
|
||||
C.free(unsafe.Pointer(legend))
|
||||
}
|
||||
C.free(unsafe.Pointer(cLegends))
|
||||
|
||||
rowCnt := (int(cEnd) - int(cStart)) / int(cStep) //+ 1 // FIXED: + 1 added extra uninitialized value
|
||||
valuesLen := colCnt * rowCnt
|
||||
values := make([]float64, valuesLen)
|
||||
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&values)))
|
||||
sliceHeader.Cap = valuesLen
|
||||
sliceHeader.Len = valuesLen
|
||||
sliceHeader.Data = uintptr(unsafe.Pointer(cData))
|
||||
return XportResult{start, end, step, legends, rowCnt, values}, nil
|
||||
}
|
||||
|
||||
// FreeValues free values memory allocated by C.
|
||||
func (r *XportResult) FreeValues() {
|
||||
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&r.values)))
|
||||
C.free(unsafe.Pointer(sliceHeader.Data))
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package rrd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type cstring byte
|
||||
|
||||
func newCstring(s string) *cstring {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *cstring) Free() {
|
||||
return
|
||||
}
|
||||
|
||||
func (cs *cstring) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Creator) create() error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (u *Updater) update(args []*cstring) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func ftoa(f float64) string {
|
||||
return strconv.FormatFloat(f, 'e', 10, 64)
|
||||
}
|
||||
|
||||
func i64toa(i int64) string {
|
||||
return strconv.FormatInt(i, 10)
|
||||
}
|
||||
|
||||
func u64toa(u uint64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func itoa(i int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func utoa(u uint) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseInfoKey(ik string) (kname, kkey string, kid int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Grapher) graph(filename string, start, end time.Time) (GraphInfo, []byte, error) {
|
||||
return GraphInfo{}, nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// Info returns information about RRD file.
|
||||
func Info(filename string) (map[string]interface{}, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// Fetch retrieves data from RRD file.
|
||||
func Fetch(filename, cf string, start, end time.Time, step time.Duration) (FetchResult, error) {
|
||||
return FetchResult{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// FreeValues free values memory allocated by C.
|
||||
func (r *FetchResult) FreeValues() {
|
||||
}
|
||||
|
||||
// Values returns copy of internal array of values.
|
||||
func (r *FetchResult) Values() []float64 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Export data from RRD file(s)
|
||||
func (e *Exporter) xport(start, end time.Time, step time.Duration) (XportResult, error) {
|
||||
return XportResult{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// FreeValues free values memory allocated by C.
|
||||
func (r *XportResult) FreeValues() {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
extern char *rrdCreate(const char *filename, unsigned long step, time_t start, int argc, const char **argv);
|
||||
extern char *rrdUpdate(const char *filename, const char *template, int argc, const char **argv);
|
||||
extern char *rrdGraph(rrd_info_t **ret, int argc, char **argv);
|
||||
extern char *rrdInfo(rrd_info_t **ret, char *filename);
|
||||
extern char *rrdFetch(int *ret, char *filename, const char *cf, time_t *start, time_t *end, unsigned long *step, unsigned long *ds_cnt, char ***ds_namv, double **data);
|
||||
extern char *rrdXport(int *ret, int argc, char **argv, int *xsize, time_t *start, time_t *end, unsigned long *step, unsigned long *col_cnt, char ***legend_v, double **data);
|
||||
extern char *arrayGetCString(char **values, int i);
|
||||
@@ -1,57 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <rrd.h>
|
||||
|
||||
char *rrdError() {
|
||||
char *err = NULL;
|
||||
if (rrd_test_error()) {
|
||||
// RRD error is local for thread so other gorutine can call some RRD
|
||||
// function in the same thread before we use C.GoString. So we need to
|
||||
// copy current error before return from C to Go. It need to be freed
|
||||
// after C.GoString in Go code.
|
||||
err = strdup(rrd_get_error());
|
||||
if (err == NULL) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
char *rrdCreate(const char *filename, unsigned long step, time_t start, int argc, const char **argv) {
|
||||
rrd_clear_error();
|
||||
rrd_create_r(filename, step, start, argc, argv);
|
||||
return rrdError();
|
||||
}
|
||||
|
||||
char *rrdUpdate(const char *filename, const char *template, int argc, const char **argv) {
|
||||
rrd_clear_error();
|
||||
rrd_update_r(filename, template, argc, argv);
|
||||
return rrdError();
|
||||
}
|
||||
|
||||
char *rrdGraph(rrd_info_t **ret, int argc, char **argv) {
|
||||
rrd_clear_error();
|
||||
*ret = rrd_graph_v(argc, argv);
|
||||
return rrdError();
|
||||
}
|
||||
|
||||
char *rrdInfo(rrd_info_t **ret, char *filename) {
|
||||
rrd_clear_error();
|
||||
*ret = rrd_info_r(filename);
|
||||
return rrdError();
|
||||
}
|
||||
|
||||
char *rrdFetch(int *ret, char *filename, const char *cf, time_t *start, time_t *end, unsigned long *step, unsigned long *ds_cnt, char ***ds_namv, double **data) {
|
||||
rrd_clear_error();
|
||||
*ret = rrd_fetch_r(filename, cf, start, end, step, ds_cnt, ds_namv, data);
|
||||
return rrdError();
|
||||
}
|
||||
|
||||
char *rrdXport(int *ret, int argc, char **argv, int *xsize, time_t *start, time_t *end, unsigned long *step, unsigned long *col_cnt, char ***legend_v, double **data) {
|
||||
rrd_clear_error();
|
||||
*ret = rrd_xport(argc, argv, xsize, start, end, step, col_cnt, legend_v, data);
|
||||
return rrdError();
|
||||
}
|
||||
|
||||
char *arrayGetCString(char **values, int i) {
|
||||
return values[i];
|
||||
}
|
||||
Reference in New Issue
Block a user