pax_global_header00006660000000000000000000000064122404203340014504gustar00rootroot0000000000000052 comment=e73d129071f2ae857531659e1401ad8897ca03f8 golang-rrd-0.0~git20131112/000077500000000000000000000000001224042033400151145ustar00rootroot00000000000000golang-rrd-0.0~git20131112/LICENSE000066400000000000000000000025621224042033400161260ustar00rootroot00000000000000Copyright (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. golang-rrd-0.0~git20131112/README.md000066400000000000000000000007531224042033400164000ustar00rootroot00000000000000# Go (golang) Bindings for 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. golang-rrd-0.0~git20131112/rrd.go000066400000000000000000000220401224042033400162300ustar00rootroot00000000000000// Simple wrapper for rrdtool C library package rrd import ( "fmt" "math" "os" "strings" "time" "unsafe" ) 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, } } func (c *Creator) DS(name, compute string, args ...interface{}) { c.args = append(c.args, "DS:"+name+":"+compute+":"+join(args)) } 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 alocations for C calls type Updater struct { filename cstring template cstring args []unsafe.Pointer } func NewUpdater(filename string) *Updater { return &Updater{filename: newCstring(filename)} } func (u *Updater) SetTemplate(dsName ...string) { 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)).p()) } // 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 { a := make([]unsafe.Pointer, 1) a[0] = newCstring(join(args)).p() return u.update(a) } else if len(u.args) != 0 { err := u.update(u.args) 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 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 color string slopeMode bool watermark string base uint imageFormat string interlaced bool args []string } const ( maxUint = ^uint(0) maxInt = int(maxUint >> 1) minInt = -maxInt - 1 ) func NewGrapher() *Grapher { return &Grapher{ upperLimit: -math.MaxFloat64, lowerLimit: math.MaxFloat64, unitsExponent: minInt, } } 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) 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.color = 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) 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:%s#%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:%s", 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 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) } 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] } golang-rrd-0.0~git20131112/rrd_c.go000066400000000000000000000260111224042033400165340ustar00rootroot00000000000000package rrd /* #include #include #include "rrdfunc.h" #cgo LDFLAGS: -lrrd_th */ import "C" import ( "math" "reflect" "strconv" "strings" "sync" "time" "unsafe" ) 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 []unsafe.Pointer) error { e := C.rrdUpdate( (*C.char)(u.filename.p()), (*C.char)(u.template.p()), 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") 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) } if g.color != "" { args = append(args, oColor, C.CString(g.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, oInterlaced, utoc(g.base)) } if g.watermark != "" { args = append(args, oWatermark, C.CString(g.watermark)) } 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)) } 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 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 values := make([]float64, valuesLen) 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)) } // 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 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)) } golang-rrd-0.0~git20131112/rrd_test.go000066400000000000000000000073461224042033400173030ustar00rootroot00000000000000package rrd import ( "fmt" "io/ioutil" "testing" "time" ) func TestAll(t *testing.T) { // Create const ( dbfile = "/tmp/test.rrd" step = 1 heartbeat = 2 * step ) c := NewCreator(dbfile, time.Now(), step) c.RRA("AVERAGE", 0.5, 1, 100) c.RRA("AVERAGE", 0.5, 5, 100) c.DS("cnt", "COUNTER", heartbeat, 0, 100) c.DS("g", "GAUGE", heartbeat, 0, 60) err := c.Create(true) if err != nil { t.Fatal(err) } // Update u := NewUpdater(dbfile) for i := 0; i < 10; i++ { time.Sleep(step * time.Second) err := u.Update(time.Now(), i, 1.5*float64(i)) if err != nil { t.Fatal(err) } } // Update with cache for i := 10; i < 20; i++ { time.Sleep(step * time.Second) u.Cache(time.Now(), i, 2*float64(i)) } err = u.Update() if err != nil { t.Fatal(err) } // Info inf, err := Info(dbfile) if err != nil { t.Fatal(err) } for k, v := range inf { fmt.Printf("%s (%T): %v\n", k, v, v) } // Graph g := NewGrapher() g.SetTitle("Test") g.SetVLabel("some variable") g.SetSize(800, 300) g.SetWatermark("some watermark") g.Def("v1", dbfile, "g", "AVERAGE") g.Def("v2", dbfile, "cnt", "AVERAGE") g.VDef("max1", "v1,MAXIMUM") g.VDef("avg2", "v2,AVERAGE") g.Line(1, "v1", "ff0000", "var 1") g.Area("v2", "0000ff", "var 2") g.GPrintT("max1", "max1 at %c") g.GPrint("avg2", "avg2=%lf") g.PrintT("max1", "max1 at %c") g.Print("avg2", "avg2=%lf") now := time.Now() i, err := g.SaveGraph("/tmp/test_rrd1.png", now.Add(-20*time.Second), now) fmt.Printf("%+v\n", i) if err != nil { t.Fatal(err) } i, buf, err := g.Graph(now.Add(-20*time.Second), now) fmt.Printf("%+v\n", i) if err != nil { t.Fatal(err) } err = ioutil.WriteFile("/tmp/test_rrd2.png", buf, 0666) if err != nil { t.Fatal(err) } // Fetch end := time.Unix(int64(inf["last_update"].(uint)), 0) start := end.Add(-20 * step * time.Second) fmt.Printf("Fetch Params:\n") fmt.Printf("Start: %s\n", start) fmt.Printf("End: %s\n", end) fmt.Printf("Step: %s\n", step*time.Second) fetchRes, err := Fetch(dbfile, "AVERAGE", start, end, step*time.Second) if err != nil { t.Fatal(err) } defer fetchRes.FreeValues() fmt.Printf("FetchResult:\n") fmt.Printf("Start: %s\n", fetchRes.Start) fmt.Printf("End: %s\n", fetchRes.End) fmt.Printf("Step: %s\n", fetchRes.Step) for _, dsName := range fetchRes.DsNames { fmt.Printf("\t%s", dsName) } fmt.Printf("\n") row := 0 for ti := fetchRes.Start.Add(fetchRes.Step); ti.Before(end) || ti.Equal(end); ti = ti.Add(fetchRes.Step) { fmt.Printf("%s / %d", ti, ti.Unix()) for i := 0; i < len(fetchRes.DsNames); i++ { v := fetchRes.ValueAt(i, row) fmt.Printf("\t%e", v) } fmt.Printf("\n") row++ } // Xport end = time.Unix(int64(inf["last_update"].(uint)), 0) start = end.Add(-20 * step * time.Second) fmt.Printf("Xport Params:\n") fmt.Printf("Start: %s\n", start) fmt.Printf("End: %s\n", end) fmt.Printf("Step: %s\n", step*time.Second) e := NewExporter() e.Def("def1", dbfile, "cnt", "AVERAGE") e.Def("def2", dbfile, "g", "AVERAGE") e.CDef("vdef1", "def1,def2,+") e.XportDef("def1", "cnt") e.XportDef("def2", "g") e.XportDef("vdef1", "sum") xportRes, err := e.Xport(start, end, step*time.Second) if err != nil { t.Fatal(err) } defer xportRes.FreeValues() fmt.Printf("XportResult:\n") fmt.Printf("Start: %s\n", xportRes.Start) fmt.Printf("End: %s\n", xportRes.End) fmt.Printf("Step: %s\n", xportRes.Step) for _, legend := range xportRes.Legends { fmt.Printf("\t%s", legend) } fmt.Printf("\n") row = 0 for ti := xportRes.Start.Add(xportRes.Step); ti.Before(end) || ti.Equal(end); ti = ti.Add(xportRes.Step) { fmt.Printf("%s / %d", ti, ti.Unix()) for i := 0; i < len(xportRes.Legends); i++ { v := xportRes.ValueAt(i, row) fmt.Printf("\t%e", v) } fmt.Printf("\n") row++ } } golang-rrd-0.0~git20131112/rrdfunc.c000066400000000000000000000032631224042033400167270ustar00rootroot00000000000000#include #include 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]; } golang-rrd-0.0~git20131112/rrdfunc.h000066400000000000000000000013211224042033400167250ustar00rootroot00000000000000extern 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);