Code

Added "sysdb" package providing core constants and types.
authorSebastian Harl <sh@tokkee.org>
Fri, 19 Sep 2014 01:46:11 +0000 (18:46 -0700)
committerSebastian Harl <sh@tokkee.org>
Fri, 19 Sep 2014 01:46:11 +0000 (18:46 -0700)
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
sysdb/core.go [new file with mode: 0644]
sysdb/store.go [new file with mode: 0644]
sysdb/store_test.go [new file with mode: 0644]

diff --git a/README b/README
index 0dbe76d86a3aec5ac133104d53cbc1108a219295..7c6c9d332f5e8ab224ef2c456ee9f7140fb3678a 100644 (file)
--- 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 (file)
index 0000000..bde2703
--- /dev/null
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
+// 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 (file)
index 0000000..9d90497
--- /dev/null
@@ -0,0 +1,249 @@
+//
+// Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
+// 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 (file)
index 0000000..bb64764
--- /dev/null
@@ -0,0 +1,145 @@
+//
+// Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
+// 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, <nil>",
+                               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 := "<nil>"
+                       if test.err {
+                               e = "<err>"
+                       }
+                       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, <nil>", 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 := "<nil>"
+                       if test.err {
+                               e = "<err>"
+                       }
+                       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 :