Code

store: Fixed decoding of DataPoint.Value fields.
[sysdb/go.git] / sysdb / store.go
1 //
2 // Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 //    notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 //    notice, this list of conditions and the following disclaimer in the
12 //    documentation and/or other materials provided with the distribution.
13 //
14 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
18 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 package sysdb
28 import (
29         "fmt"
30         "time"
31 )
33 // The SysDB JSON time format.
34 const jsonTime = `"2006-01-02 15:04:05 -0700"`
36 // A Duration represents the elapsed time between two instants as a
37 // nanoseconds count.
38 //
39 // It supports marshaling to and unmarshaling from the SysDB JSON format (a
40 // sequence of decimal numbers with a unit suffix).
41 type Duration time.Duration
43 const (
44         Second = Duration(1000000000)
45         Minute = 60 * Second
46         Hour   = 60 * Minute
47         Day    = 24 * Hour
48         Month  = Duration(30436875 * 24 * 60 * 60 * 1000)
49         Year   = Duration(3652425 * 24 * 60 * 60 * 100000)
50 )
52 // MarshalJSON implements the json.Marshaler interface. The time is a quoted
53 // string in the SysDB JSON format.
54 func (d Duration) MarshalJSON() ([]byte, error) {
55         if d == 0 {
56                 return []byte(`"0s"`), nil
57         }
59         s := `"`
60         secs := false
61         for _, spec := range []struct {
62                 interval Duration
63                 suffix   string
64         }{{Year, "Y"}, {Month, "M"}, {Day, "D"}, {Hour, "h"}, {Minute, "m"}, {Second, ""}} {
65                 if d >= spec.interval {
66                         s += fmt.Sprintf("%d%s", d/spec.interval, spec.suffix)
67                         d %= spec.interval
68                         if spec.interval == Second {
69                                 secs = true
70                         }
71                 }
72         }
74         if d > 0 {
75                 s += fmt.Sprintf(".%09d", d)
76                 for i := len(s) - 1; i > 0; i-- {
77                         if s[i] != '0' {
78                                 break
79                         }
80                         s = s[:i]
81                 }
82                 secs = true
83         }
84         if secs {
85                 s += "s"
86         }
87         s += `"`
88         return []byte(s), nil
89 }
91 // UnmarshalJSON implements the json.Unmarshaler interface. The duration is
92 // expected to be a quoted string in the SysDB JSON format.
93 func (d *Duration) UnmarshalJSON(data []byte) error {
94         m := map[string]Duration{
95                 "Y": Year,
96                 "M": Month,
97                 "D": Day,
98                 "h": Hour,
99                 "m": Minute,
100                 "s": Second,
101         }
103         if data[0] != '"' || data[len(data)-1] != '"' {
104                 return fmt.Errorf("unquoted duration %q", string(data))
105         }
106         data = data[1 : len(data)-1]
108         orig := string(data)
109         var res Duration
110         for len(data) != 0 {
111                 // consume digits
112                 n := 0
113                 dec := 0
114                 frac := false
115                 for n < len(data) && '0' <= data[n] && data[n] <= '9' {
116                         dec = dec*10 + int(data[n]-'0')
117                         n++
118                 }
119                 if n < len(data) && data[n] == '.' {
120                         frac = true
121                         n++
123                         // consume fraction
124                         m := 1000000000
125                         for n < len(data) && '0' <= data[n] && data[n] <= '9' {
126                                 if m > 1 { // cut of to nanoseconds
127                                         dec = dec*10 + int(data[n]-'0')
128                                         m /= 10
129                                 }
130                                 n++
131                         }
132                         dec *= m
133                 }
134                 if n >= len(data) {
135                         return fmt.Errorf("missing unit in duration %q", orig)
136                 }
137                 if n == 0 {
138                         // we found something which is not a number
139                         return fmt.Errorf("invalid duration %q", orig)
140                 }
142                 // consume unit
143                 u := n
144                 for u < len(data) && data[u] != '.' && (data[u] < '0' || '9' < data[u]) {
145                         u++
146                 }
148                 unit := string(data[n:u])
149                 data = data[u:]
151                 // convert to Duration
152                 d, ok := m[unit]
153                 if !ok {
154                         return fmt.Errorf("invalid unit %q in duration %q", unit, orig)
155                 }
157                 if d == Second {
158                         if frac {
159                                 d = 1
160                         }
161                 } else if frac {
162                         return fmt.Errorf("invalid fraction %q%s in duration %q", dec, unit, orig)
163                 }
165                 res += Duration(dec) * d
166         }
167         *d = res
168         return nil
171 // String returns the duration formatted using a predefined format string.
172 func (d Duration) String() string { return time.Duration(d).String() }
174 // A Time represents an instant in time with nanosecond precision.
175 //
176 // It supports marshaling to and unmarshaling from the SysDB JSON format
177 // (YYYY-MM-DD hh:mm:ss +-zzzz).
178 type Time time.Time
180 func (t Time) MarshalJSON() ([]byte, error) {
181         return []byte(time.Time(t).Format(jsonTime)), nil
184 // UnmarshalJSON implements the json.Unmarshaler interface. The time is
185 // expected to be a quoted string in the SysDB JSON format.
186 func (t *Time) UnmarshalJSON(data []byte) error {
187         parsed, err := time.Parse(jsonTime, string(data))
188         if err == nil {
189                 *t = Time(parsed)
190         }
191         return err
194 // Equal reports whether t and u represent the same time instant.
195 func (t Time) Equal(u Time) bool {
196         return time.Time(t).Equal(time.Time(u))
199 // String returns the time formatted using a predefined format string.
200 func (t Time) String() string { return time.Time(t).String() }
202 // An Attribute describes a host, metric, or service attribute.
203 type Attribute struct {
204         Name           string   `json:"name"`
205         Value          string   `json:"value"`
206         LastUpdate     Time     `json:"last_update"`
207         UpdateInterval Duration `json:"update_interval"`
208         Backends       []string `json:"backends"`
211 // A Metric describes a metric known to SysDB.
212 type Metric struct {
213         Name           string      `json:"name"`
214         LastUpdate     Time        `json:"last_update"`
215         UpdateInterval Duration    `json:"update_interval"`
216         Backends       []string    `json:"backends"`
217         Attributes     []Attribute `json:"attributes"`
220 // A Service describes a service object stored in the SysDB store.
221 type Service struct {
222         Name           string      `json:"name"`
223         LastUpdate     Time        `json:"last_update"`
224         UpdateInterval Duration    `json:"update_interval"`
225         Backends       []string    `json:"backends"`
226         Attributes     []Attribute `json:"attributes"`
229 // A Host describes a host object stored in the SysDB store.
230 type Host struct {
231         Name           string      `json:"name"`
232         LastUpdate     Time        `json:"last_update"`
233         UpdateInterval Duration    `json:"update_interval"`
234         Backends       []string    `json:"backends"`
235         Attributes     []Attribute `json:"attributes"`
236         Metrics        []Metric    `json:"metrics"`
237         Services       []Service   `json:"services"`
240 // A DataPoint describes a datum at a certain point of time.
241 type DataPoint struct {
242         Timestamp Time    `json:"timestamp"`
243         Value     float64 `json:"value,string"`
246 // A Timeseries describes a sequence of data-points.
247 type Timeseries struct {
248         Start Time                   `json:"start"`
249         End   Time                   `json:"end"`
250         Data  map[string][]DataPoint `json:"data"`
253 // vim: set tw=78 sw=4 sw=4 noexpandtab :