From 72a60afdded7d2751bfdc392931e96ec70dfa852 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Thu, 18 Sep 2014 18:46:11 -0700 Subject: [PATCH] Added "sysdb" package providing core constants and types. For now, provide constants for the log priorities and types describing stored objects and time-series. All store related types support marshaling to and unmarshaling from JSON. --- README | 3 + sysdb/core.go | 42 ++++++++ sysdb/store.go | 249 ++++++++++++++++++++++++++++++++++++++++++++ sysdb/store_test.go | 145 ++++++++++++++++++++++++++ 4 files changed, 439 insertions(+) create mode 100644 sysdb/core.go create mode 100644 sysdb/store.go create mode 100644 sysdb/store_test.go diff --git a/README b/README index 0dbe76d..7c6c9d3 100644 --- a/README +++ b/README @@ -54,6 +54,9 @@ Packages protocol. That's the protocol used for communication between a client and a SysDB server instance. + * github.com/sysdb/go/sysdb: Core constants and types used by SysDB + packages. + Getting Help ------------ diff --git a/sysdb/core.go b/sysdb/core.go new file mode 100644 index 0000000..bde2703 --- /dev/null +++ b/sysdb/core.go @@ -0,0 +1,42 @@ +// +// Copyright (C) 2014 Sebastian 'tokkee' Harl +// 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. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// ``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 COPYRIGHT HOLDERS OR +// CONTRIBUTORS 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. + +// Package sysdb declares core constants and types used by SysDB packages. +package sysdb + +// The LogPriority describes the priority of a log message. +type LogPriority int + +const ( + // Log priorities supported by SysDB. + LogEmerg = LogPriority(0) + LogErr = LogPriority(3) + LogWarning = LogPriority(4) + LogNotice = LogPriority(5) + LogInfo = LogPriority(6) + LogDebug = LogPriority(7) +) + +// vim: set tw=78 sw=4 sw=4 noexpandtab : diff --git a/sysdb/store.go b/sysdb/store.go new file mode 100644 index 0000000..9d90497 --- /dev/null +++ b/sysdb/store.go @@ -0,0 +1,249 @@ +// +// Copyright (C) 2014 Sebastian 'tokkee' Harl +// 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. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// ``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 COPYRIGHT HOLDERS OR +// CONTRIBUTORS 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. + +package sysdb + +import ( + "fmt" + "time" +) + +// The SysDB JSON time format. +const jsonTime = `"2006-01-02 15:04:05 -0700"` + +// A Duration represents the elapsed time between two instants as a +// nanoseconds count. +// +// It supports marshaling to and unmarshaling from the SysDB JSON format (a +// sequence of decimal numbers with a unit suffix). +type Duration time.Duration + +const ( + Second = Duration(1000000000) + Minute = 60 * Second + Hour = 60 * Minute + Day = 24 * Hour + Month = Duration(30436875 * 24 * 60 * 60 * 1000) + Year = Duration(3652425 * 24 * 60 * 60 * 100000) +) + +// MarshalJSON implements the json.Marshaler interface. The time is a quoted +// string in the SysDB JSON format. +func (d Duration) MarshalJSON() ([]byte, error) { + if d == 0 { + return []byte(`"0s"`), nil + } + + s := `"` + secs := false + for _, spec := range []struct { + interval Duration + suffix string + }{{Year, "Y"}, {Month, "M"}, {Day, "D"}, {Hour, "h"}, {Minute, "m"}, {Second, ""}} { + if d >= spec.interval { + s += fmt.Sprintf("%d%s", d/spec.interval, spec.suffix) + d %= spec.interval + if spec.interval == Second { + secs = true + } + } + } + + if d > 0 { + s += fmt.Sprintf(".%09d", d) + for i := len(s) - 1; i > 0; i-- { + if s[i] != '0' { + break + } + s = s[:i] + } + secs = true + } + if secs { + s += "s" + } + s += `"` + return []byte(s), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. The duration is +// expected to be a quoted string in the SysDB JSON format. +func (d *Duration) UnmarshalJSON(data []byte) error { + m := map[string]Duration{ + "Y": Year, + "M": Month, + "D": Day, + "h": Hour, + "m": Minute, + "s": Second, + } + + if data[0] != '"' || data[len(data)-1] != '"' { + return fmt.Errorf("unquoted duration %q", string(data)) + } + data = data[1 : len(data)-1] + + orig := string(data) + var res Duration + for len(data) != 0 { + // consume digits + n := 0 + dec := 0 + frac := false + for n < len(data) && '0' <= data[n] && data[n] <= '9' { + dec = dec*10 + int(data[n]-'0') + n++ + } + if n < len(data) && data[n] == '.' { + frac = true + n++ + + // consume fraction + m := 1000000000 + for n < len(data) && '0' <= data[n] && data[n] <= '9' { + if m > 1 { // cut of to nanoseconds + dec = dec*10 + int(data[n]-'0') + m /= 10 + } + n++ + } + dec *= m + } + if n >= len(data) { + return fmt.Errorf("missing unit in duration %q", orig) + } + if n == 0 { + // we found something which is not a number + return fmt.Errorf("invalid duration %q", orig) + } + + // consume unit + u := n + for u < len(data) && data[u] != '.' && (data[u] < '0' || '9' < data[u]) { + u++ + } + + unit := string(data[n:u]) + data = data[u:] + + // convert to Duration + d, ok := m[unit] + if !ok { + return fmt.Errorf("invalid unit %q in duration %q", unit, orig) + } + + if d == Second { + if frac { + d = 1 + } + } else if frac { + return fmt.Errorf("invalid fraction %q%s in duration %q", dec, unit, orig) + } + + res += Duration(dec) * d + } + *d = res + return nil +} + +// String returns the duration formatted using a predefined format string. +func (d Duration) String() string { return time.Duration(d).String() } + +// A Time represents an instant in time with nanosecond precision. +// +// It supports marshaling to and unmarshaling from the SysDB JSON format +// (YYYY-MM-DD hh:mm:ss +-zzzz). +type Time time.Time + +func (t Time) MarshalJSON() ([]byte, error) { + return []byte(time.Time(t).Format(jsonTime)), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. The time is +// expected to be a quoted string in the SysDB JSON format. +func (t *Time) UnmarshalJSON(data []byte) error { + parsed, err := time.Parse(jsonTime, string(data)) + if err == nil { + *t = Time(parsed) + } + return err +} + +// Equal reports whether t and u represent the same time instant. +func (t Time) Equal(u Time) bool { + return time.Time(t).Equal(time.Time(u)) +} + +// String returns the time formatted using a predefined format string. +func (t Time) String() string { return time.Time(t).String() } + +// An Attribute describes a host, metric, or service attribute. +type Attribute struct { + Name string `json:"name"` + Value string `json:"value"` + LastUpdate Time `json:"last_update"` + UpdateInterval Duration `json:"update_interval"` +} + +// A Metric describes a metric known to SysDB. +type Metric struct { + Name string `json:"name"` + LastUpdate Time `json:"last_update"` + UpdateInterval Duration `json:"update_interval"` + Attributes []Attribute `json:"attributes"` +} + +// A Service describes a service object stored in the SysDB store. +type Service struct { + Name string `json:"name"` + LastUpdate Time `json:"last_update"` + UpdateInterval Duration `json:"update_interval"` + Attributes []Attribute `json:"attributes"` +} + +// A Host describes a host object stored in the SysDB store. +type Host struct { + Name string `json:"name"` + LastUpdate Time `json:"last_update"` + UpdateInterval Duration `json:"update_interval"` + Attributes []Attribute `json:"attributes"` + Metrics []Metric `json:"metrics"` + Services []Service `json:"services"` +} + +// A DataPoint describes a datum at a certain point of time. +type DataPoint struct { + Timestamp Time `json:"timestamp"` + Value float64 `json:"value"` +} + +// A Timeseries describes a sequence of data-points. +type Timeseries struct { + Start Time `json:"start"` + End Time `json:"end"` + Data map[string][]DataPoint +} + +// vim: set tw=78 sw=4 sw=4 noexpandtab : diff --git a/sysdb/store_test.go b/sysdb/store_test.go new file mode 100644 index 0000000..bb64764 --- /dev/null +++ b/sysdb/store_test.go @@ -0,0 +1,145 @@ +// +// Copyright (C) 2014 Sebastian 'tokkee' Harl +// 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. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// ``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 COPYRIGHT HOLDERS OR +// CONTRIBUTORS 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. + +package sysdb + +import ( + "testing" + "time" +) + +func TestMarshalDuration(t *testing.T) { + for _, test := range []struct { + d Duration + expected string + }{ + {Duration(0), `"0s"`}, + {Duration(123), `".000000123s"`}, + {Duration(1000123000), `"1.000123s"`}, + {Duration(47940228000000000), `"1Y6M7D"`}, + {Second, `"1s"`}, + {Minute, `"1m"`}, + {Hour, `"1h"`}, + {Day, `"1D"`}, + {Month, `"1M"`}, + {Year, `"1Y"`}, + {Year + Day + Minute, `"1Y1D1m"`}, + } { + got, err := test.d.MarshalJSON() + if err != nil || string(got) != test.expected { + t.Errorf("%s.MarshalJSON() = %s, %v; want %s, ", + test.d, string(got), err, test.expected) + } + } +} + +func TestUnmarshalDuration(t *testing.T) { + for _, test := range []struct { + data string + expected Duration + err bool + }{ + {"0s", 0, true}, // unquoted + {`"0"`, 0, true}, // missing unit + {`"0.0"`, 0, true}, + {`".0"`, 0, true}, + {`"s"`, 0, true}, // missing decimal + {`"1y"`, 0, true}, // invalid unit + {`"abc"`, 0, true}, // all invalid + {`"0s"`, 0, false}, + {`"1.0s"`, Second, false}, + {`".5s"`, 500000000, false}, + {`"1.000123s"`, 1000123000, false}, + {`"1.0001234s"`, 1000123400, false}, + {`"1.00012345s"`, 1000123450, false}, + {`"1.000123456s"`, 1000123456, false}, + {`"1.0001234567s"`, 1000123456, false}, + {`"1.000123000123s"`, 1000123000, false}, + {`"1Y6M7D"`, 47940228000000000, false}, + {`"1s"`, Second, false}, + {`"1m"`, Minute, false}, + {`"1h"`, Hour, false}, + {`"1D"`, Day, false}, + {`"1M"`, Month, false}, + {`"1Y"`, Year, false}, + } { + var d Duration + err := d.UnmarshalJSON([]byte(test.data)) + if (err != nil) != test.err || d != test.expected { + e := "" + if test.err { + e = "" + } + t.Errorf("UnmarshalJSON(%s) = %v (%s); want %s (%s)", + test.data, err, d, e, test.expected) + } + } +} + +func TestMarshalTime(t *testing.T) { + tm := Time(time.Date(2014, 9, 18, 23, 42, 12, 123, time.UTC)) + expected := `"2014-09-18 23:42:12 +0000"` + got, err := tm.MarshalJSON() + if err != nil || string(got) != expected { + t.Errorf("%s.MarshalJSON() = %s, %v; %s, ", tm, got, err, expected) + } +} + +func TestUnmarshalTime(t *testing.T) { + for _, test := range []struct { + data string + expected Time + err bool + }{ + { + `"2014-09-18 23:42:12 +0000"`, + Time(time.Date(2014, 9, 18, 23, 42, 12, 0, time.UTC)), + false, + }, + { + `2014-09-18 23:42:12 +0000`, + Time{}, + true, + }, + { + `"2014-09-18T23:42:12Z00:00"`, + Time{}, + true, + }, + } { + var tm Time + err := tm.UnmarshalJSON([]byte(test.data)) + if (err != nil) != test.err || !tm.Equal(test.expected) { + e := "" + if test.err { + e = "" + } + t.Errorf("UnmarshalJSON(%s) = %v (%s); want %s (%s)", + test.data, err, tm, e, test.expected) + } + } +} + +// vim: set tw=78 sw=4 sw=4 noexpandtab : -- 2.30.2