diff options
Diffstat (limited to 'rrdformat/format.go')
-rw-r--r-- | rrdformat/format.go | 210 |
1 files changed, 53 insertions, 157 deletions
diff --git a/rrdformat/format.go b/rrdformat/format.go index 4e68b93..71bd966 100644 --- a/rrdformat/format.go +++ b/rrdformat/format.go @@ -10,15 +10,43 @@ import ( "git.lukeshu.com/go/librrd/rrdformat/rrdbinary" ) +// File format versions: +// +// For the most part, the layout of the file format hasn't +// changed--the version number just indicates which enum members you +// need to support. The only parser-breaking change is that the +// 0002->0003 transition added a few bytes to the timestamp, in order +// to go from second-precision to microsecond-precision. +// +// - "0005" (added in rrdtool 1.5.0, 2015-04-16) +// * added DST_DCOUNTER +// * added DST_DDERIVE +// * otherwise identical to "0004" +// +// - "0004" (added in rrdtool 1.3.0, 2008-06-10) +// * added MHWPREDICT function +// * otherwise identical to "0003" +// +// - "0003" (added in the unreleased rrdtool 1.1.x in 2003, +// present in rrdtool 1.2.0, 2005-04-25) +// * last-updated timestamp gained a `long` microsecond component +// * otherwise identical to "0002" +// +// - "0002" (never used by a stable release of rrdtool, but was used +// by development versions of 1.1.x 2001-2003) +// * added DST_CDEF +// * added CF_HWPREDICT +// * added CF_SEASONAL +// * added CF_DEVPREDICT +// * added CF_DEVSEASONAL +// * added CF_FAILURES +// +// - "0001" (the original in rrdtool 1.0.0, 1999-07-15) + const XMLNS = "https://oss.oetiker.ch/rrdtool/rrdtool-dump.xml" type RRDValue = rrdbinary.Float -// rrdtool: -// rrd_format.h: stat_head_t -- static header of the database -// -// javascriptRRD: -// rrdFile.js: RRDHeader type Header struct { Cookie rrdbinary.String `rrdbinary:"size=3" xml:"-"` Version rrdbinary.String `rrdbinary:"size=4" xml:"version"` @@ -41,6 +69,11 @@ type RRADef struct { PDPCnt rrdBinary.Uint } +type Timestamp struct { + Sec rrdbinary.Time + Usec rrdbinary.Int // signed, but always >= 0 +} + type PDPPrep struct { LastDS rrdbinary.String `rrdbinary:"size=30"` Scratch [10]rrdbinary.Unival @@ -54,166 +87,29 @@ type RRAPtr struct { CurRow rrdbinary.Uint } -// rrdtool: -// rrd_format.h: rrd_t One single struct to hold all the others. -type RRD struct { +type RRDv0005 = RRDv0004 +type RRDv0004 = RRDv0003 + +type RRDv0003 struct { Header Header DSDefs []DSDef RRADefs []RRADef - LastUpdated rrdbinary.Timesstamp + LastUpdated Timestamp PDPPrep TODO CPDPrep TODO RRAPtr TODO Values []RRDValue } -type Header struct { - // identification section - Cookie []byte // 4 bytes - Version []byte // 4 bytes (eh, we let the \0-terminator decided how long) - // padding - FloatCookie []byte // 8 bytes - - Bytes - - // structure definition - DSCnt uint64 // how many different DS provide input to the RRD - RRACnt uint64 // how many RRAs will be maintained in the RRD - PDPStep uint64 // PDP interval in seconds - Parameters [10]Unival - - // sniffed metadata - ByteOrder binary.ByteOrder - FloatWidth int - FloatAlign int - IntWidth int - IntAlign int - UnivalWidth int - UnivalAlign int -} +type RRDv0002 = RRDv0001 -func (h *Header) UnmarshalBinary(data []byte) error { - // magic number cookie - if !bytes.HasPrefix(data, []byte("RRD\x00")) { - return newBinError("not an RRD file: wrong magic number", data, 0, 4) - } - h.Cookie = data[0:4] - - // version string - null := bytes.IndexByte(data[4:], 0) - if null < 0 { - return newBinError("no null-terminator on version string", data, 4, 5) - } - null += 4 - h.Version = data[4:null] - switch string(h.Version) { - case "0003": - case "0004": - case "0005": - default: - } - - // float cookie - // - // Assume IEEE 754 doubles. C doesn't assume 754 doubles, but anything that doesn't use 754 doubles is exotic - // enough that I'm OK saying "you're going to need to use `rrdtool dump`". This lets us assume that: - // - a 'double' is 8 bytes wide - // - the value will be exactly equal, and we don't need to worry about weird rounding. - h.FloatWidth = 8 - magicFloat := float64(8.642135e130) - floatAddrPacked := null + 1 - floatAddr32 := ((floatAddrPacked + 3) / 4) * 4 - floatAddr64 := ((floatAddrPacked + 7) / 8) * 8 - var restOffset int - switch { - case len(data) < floatAddr32+h.FloatWidth: - return newBinError("unexpected end of file", data, floatAddrPacked, floatAddr64+h.FloatWidth-floatAddrPacked) - case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr32:])) == magicFloat: - h.FloatCookie = data[floatAddr32 : floatAddr32+h.FloatWidth] - h.ByteOrder = binary.LittleEndian - h.FloatAlign = 4 - restOffset = floatAddr32 + h.FloatWidth - case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr32:])) == magicFloat: - h.FloatCookie = data[floatAddr32 : floatAddr32+h.FloatWidth] - h.ByteOrder = binary.BigEndian - h.FloatAlign = 4 - restOffset = floatAddr32 + h.FloatWidth - case len(data) < floatAddr64+h.FloatWidth: - return newBinError("unexpected end of file", data, floatAddrPacked, floatAddr64+h.FloatWidth-floatAddrPacked) - case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr64:])) == magicFloat: - h.FloatCookie = data[floatAddr64 : floatAddr64+h.FloatWidth] - h.ByteOrder = binary.LittleEndian - h.FloatAlign = 8 - restOffset = floatAddr64 + h.FloatWidth - case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr64:])) == magicFloat: - h.FloatCookie = data[floatAddr64 : floatAddr64+h.FloatWidth] - h.ByteOrder = binary.BigEndian - h.FloatAlign = 8 - restOffset = floatAddr64 + h.FloatWidth - default: - return newBinError("failed to sniff byte-order and float-alignment", - data, floatAddrPacked, floatAddr64+h.FloatWidth-floatAddrPacked) - } - - switch h.FloatAlign { - case 4: - // Assume that if floats are only 32-bit aligned, then everything is 32-bit - h.IntWidth = 4 - h.IntAlign = 4 - case 8: - // If floats are 64-bit aligned, then this might be all-in on 64-bit, or it might 32-bit ints. - - // (The following heuristic is borrowed from javascriptRRD, and adjusted to also work with big-endian.) - // - // The next 2 things after the float_cookie are ds_cnt and rra_cnt (both 'unsigned long'--which may be - // either 32 or 64 bit). We'll inspect the bytes a bit to guess how long a long is. - // - // By assuming - // 1. ds_cnt <= math.MaxUint32 - // 2l. rra_cnt > 0 (relevant if little-endian) - // 2b. ds_cnt > 0 (relevant if big-endian) - // we can inspect the 4 bytes (marked "big" or "little" below) that are either - // - the most significant bits of 64-bit ds_cnt, or - // - the entirety of 32-bit rra_cnt (if littlen-endian) or 32-bit ds_cnt (if big-endian) - // If we see that those 4 bytes are all 0, then we assume that it's part of a 64-bit ds_cnt. - // - // | | | | | | | big | little | - // |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| - // 32 | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<--ds_cnt->|<-rra_cnt->| - // 64le | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<1111----ds_cnt---0000>| - // 64be | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<0000----ds_cnt---1111>| - if len(data) < restOffset+8 { - return newBinError("unexpected end of file", data, restOffset, 8) - } - offset := map[binary.ByteOrder]int{ - binary.BigEndian: restOffset, // 24 in the above diagram - binary.LittleEndian: restOffset + 4, // 28 in the above diagram - }[h.ByteOrder] - if h.ByteOrder.Uint32(data[offset:]) == 0 { - h.IntWidth = 8 - h.IntAlign = 8 - } else { - h.IntWidth = 4 - h.IntAlign = 4 - } - } - - return nil -} - -func (h *Header) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - if err := e.EncodeElement(h.Version, xml.StartElement{Name: xml.Name{Local: "version", Space: XMLNS}}); err != nil { - return err - } - if err := e.EncodeElement(h.PDPStep, xml.StartElement{Name: xml.Name{Local: "step", Space: XMLNS}}); err != nil { - return err - } - return nil +type RRDv0001 struct { + Header Header + DSDefs []DSDef + RRADefs []RRADef + LastUpdated rrdbinary.Timestamp + PDPPrep TODO + CPDPrep TODO + RRAPtr TODO + Values []RRDValue } - -//var _ encoding.BinaryMarshaler = &Header{} -var _ encoding.BinaryUnmarshaler = &Header{} - -var _ xml.Marshaler = &Header{} - -//var _ xml.Unmarshaler = &Header{} |