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 // Common durations. All values greater than or equal to a day are not exact
44 // values but subject to daylight savings time changes, leap years, etc. They
45 // are available mostly for providing human readable display formats.
46 const (
47 Second = Duration(1000000000)
48 Minute = 60 * Second
49 Hour = 60 * Minute
50 Day = 24 * Hour
51 Month = Duration(30436875 * 24 * 60 * 60 * 1000)
52 Year = Duration(3652425 * 24 * 60 * 60 * 100000)
53 )
55 // MarshalJSON implements the json.Marshaler interface. The duration is a
56 // quoted string in the SysDB JSON format.
57 func (d Duration) MarshalJSON() ([]byte, error) {
58 if d == 0 {
59 return []byte(`"0s"`), nil
60 }
62 s := `"`
63 secs := false
64 for _, spec := range []struct {
65 interval Duration
66 suffix string
67 }{{Year, "Y"}, {Month, "M"}, {Day, "D"}, {Hour, "h"}, {Minute, "m"}, {Second, ""}} {
68 if d >= spec.interval {
69 s += fmt.Sprintf("%d%s", d/spec.interval, spec.suffix)
70 d %= spec.interval
71 if spec.interval == Second {
72 secs = true
73 }
74 }
75 }
77 if d > 0 {
78 s += fmt.Sprintf(".%09d", d)
79 for i := len(s) - 1; i > 0; i-- {
80 if s[i] != '0' {
81 break
82 }
83 s = s[:i]
84 }
85 secs = true
86 }
87 if secs {
88 s += "s"
89 }
90 s += `"`
91 return []byte(s), nil
92 }
94 // UnmarshalJSON implements the json.Unmarshaler interface. The duration is
95 // expected to be a quoted string in the SysDB JSON format.
96 func (d *Duration) UnmarshalJSON(data []byte) error {
97 m := map[string]Duration{
98 "Y": Year,
99 "M": Month,
100 "D": Day,
101 "h": Hour,
102 "m": Minute,
103 "s": Second,
104 }
106 if data[0] != '"' || data[len(data)-1] != '"' {
107 return fmt.Errorf("unquoted duration %q", string(data))
108 }
109 data = data[1 : len(data)-1]
111 orig := string(data)
112 var res Duration
113 for len(data) != 0 {
114 // consume digits
115 n := 0
116 dec := 0
117 frac := false
118 for n < len(data) && '0' <= data[n] && data[n] <= '9' {
119 dec = dec*10 + int(data[n]-'0')
120 n++
121 }
122 if n < len(data) && data[n] == '.' {
123 frac = true
124 n++
126 // consume fraction
127 m := 1000000000
128 for n < len(data) && '0' <= data[n] && data[n] <= '9' {
129 if m > 1 { // cut of to nanoseconds
130 dec = dec*10 + int(data[n]-'0')
131 m /= 10
132 }
133 n++
134 }
135 dec *= m
136 }
137 if n >= len(data) {
138 return fmt.Errorf("missing unit in duration %q", orig)
139 }
140 if n == 0 {
141 // we found something which is not a number
142 return fmt.Errorf("invalid duration %q", orig)
143 }
145 // consume unit
146 u := n
147 for u < len(data) && data[u] != '.' && (data[u] < '0' || '9' < data[u]) {
148 u++
149 }
151 unit := string(data[n:u])
152 data = data[u:]
154 // convert to Duration
155 d, ok := m[unit]
156 if !ok {
157 return fmt.Errorf("invalid unit %q in duration %q", unit, orig)
158 }
160 if d == Second {
161 if frac {
162 d = 1
163 }
164 } else if frac {
165 return fmt.Errorf("invalid fraction %q%s in duration %q", dec, unit, orig)
166 }
168 res += Duration(dec) * d
169 }
170 *d = res
171 return nil
172 }
174 // String returns the duration formatted using a predefined format string.
175 func (d Duration) String() string { return time.Duration(d).String() }
177 // A Time represents an instant in time with nanosecond precision.
178 //
179 // It supports marshaling to and unmarshaling from the SysDB JSON format
180 // (YYYY-MM-DD hh:mm:ss +-zzzz).
181 type Time time.Time
183 // MarshalJSON implements the json.Marshaler interface. The time is a quoted
184 // string in the SysDB JSON format.
185 func (t Time) MarshalJSON() ([]byte, error) {
186 return []byte(time.Time(t).Format(jsonTime)), nil
187 }
189 // UnmarshalJSON implements the json.Unmarshaler interface. The time is
190 // expected to be a quoted string in the SysDB JSON format.
191 func (t *Time) UnmarshalJSON(data []byte) error {
192 parsed, err := time.Parse(jsonTime, string(data))
193 if err == nil {
194 *t = Time(parsed)
195 }
196 return err
197 }
199 // Equal reports whether t and u represent the same time instant.
200 func (t Time) Equal(u Time) bool {
201 return time.Time(t).Equal(time.Time(u))
202 }
204 // String returns the time formatted using a predefined format string.
205 func (t Time) String() string { return time.Time(t).String() }
207 // An Attribute describes a host, metric, or service attribute.
208 type Attribute struct {
209 Name string `json:"name"`
210 Value string `json:"value"`
211 LastUpdate Time `json:"last_update"`
212 UpdateInterval Duration `json:"update_interval"`
213 Backends []string `json:"backends"`
214 }
216 // A Metric describes a metric known to SysDB.
217 type Metric struct {
218 Name string `json:"name"`
219 Timeseries bool `json:"timeseries"`
220 LastUpdate Time `json:"last_update"`
221 UpdateInterval Duration `json:"update_interval"`
222 Backends []string `json:"backends"`
223 Attributes []Attribute `json:"attributes"`
224 }
226 // A Service describes a service object stored in the SysDB store.
227 type Service struct {
228 Name string `json:"name"`
229 LastUpdate Time `json:"last_update"`
230 UpdateInterval Duration `json:"update_interval"`
231 Backends []string `json:"backends"`
232 Attributes []Attribute `json:"attributes"`
233 }
235 // A Host describes a host object stored in the SysDB store.
236 type Host struct {
237 Name string `json:"name"`
238 LastUpdate Time `json:"last_update"`
239 UpdateInterval Duration `json:"update_interval"`
240 Backends []string `json:"backends"`
241 Attributes []Attribute `json:"attributes"`
242 Metrics []Metric `json:"metrics"`
243 Services []Service `json:"services"`
244 }
246 // A DataPoint describes a datum at a certain point of time.
247 type DataPoint struct {
248 Timestamp Time `json:"timestamp"`
249 Value float64 `json:"value,string"`
250 }
252 // A Timeseries describes a sequence of data-points.
253 type Timeseries struct {
254 Start Time `json:"start"`
255 End Time `json:"end"`
256 Data map[string][]DataPoint `json:"data"`
257 }
259 // vim: set tw=78 sw=4 sw=4 noexpandtab :