Remove RRD dependency and switch to dynamic linking

This commit is contained in:
divolgin
2020-11-12 19:13:25 +00:00
parent 4e74fd209e
commit 9e669b3f13
16 changed files with 4 additions and 1696 deletions

View File

@@ -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} \

View File

@@ -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.

View File

@@ -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: {}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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]
}

View File

@@ -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() {
}

View File

@@ -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))
}

View File

@@ -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() {
}

View File

@@ -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);

View File

@@ -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];
}