Code

Parse a metric's "timeseries" field.
[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 // 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
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
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
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))
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"`
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"`
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"`
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"`
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"`
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"`
259 // vim: set tw=78 sw=4 sw=4 noexpandtab :