diff --git a/cmd/bosun/sched/bolt.go b/cmd/bosun/sched/bolt.go index 871295aaeb..12ee37fedb 100644 --- a/cmd/bosun/sched/bolt.go +++ b/cmd/bosun/sched/bolt.go @@ -49,6 +49,7 @@ const ( dbSilence = "silence" dbStatus = "status" dbMetadata = "metadata" + dbMetricMetadata = "metadata-metric" dbIncidents = "incidents" ) @@ -58,15 +59,16 @@ func (s *Schedule) save() { } s.Lock("Save") store := map[string]interface{}{ - dbMetric: s.Search.Read.Metric, - dbTagk: s.Search.Read.Tagk, - dbTagv: s.Search.Read.Tagv, - dbMetricTags: s.Search.Read.MetricTags, - dbNotifications: s.Notifications, - dbSilence: s.Silence, - dbStatus: s.status, - dbMetadata: s.Metadata, - dbIncidents: s.Incidents, + dbMetric: s.Search.Read.Metric, + dbTagk: s.Search.Read.Tagk, + dbTagv: s.Search.Read.Tagv, + dbMetricTags: s.Search.Read.MetricTags, + dbNotifications: s.Notifications, + dbSilence: s.Silence, + dbStatus: s.status, + dbMetadata: s.Metadata, + dbIncidents: s.Incidents, + dbMetricMetadata: s.metricMetadata, } tostore := make(map[string][]byte) for name, data := range store { @@ -145,6 +147,18 @@ func (s *Schedule) RestoreState() error { defer gr.Close() return gob.NewDecoder(gr).Decode(dst) } + if err := decode(dbMetadata, &s.Metadata); err != nil { + slog.Errorln(dbMetadata, err) + } + if err := decode(dbMetricMetadata, &s.metricMetadata); err != nil { + slog.Errorln(dbMetricMetadata, err) + } + for k, v := range s.Metadata { + if k.Name == "desc" || k.Name == "rate" || k.Name == "unit" { + s.PutMetadata(k, v.Value) + delete(s.Metadata, k) + } + } if err := decode(dbMetric, &s.Search.Metric); err != nil { slog.Errorln(dbMetric, err) } @@ -167,6 +181,7 @@ func (s *Schedule) RestoreState() error { if err := decode(dbIncidents, &s.Incidents); err != nil { slog.Errorln(dbIncidents, err) } + // Calculate next incident id. for _, i := range s.Incidents { if i.Id > s.maxIncidentId { @@ -224,12 +239,10 @@ func (s *Schedule) RestoreState() error { s.AddNotification(ak, n, t) } } - if err := decode(dbMetadata, &s.Metadata); err != nil { - slog.Errorln(dbMetadata, err) - } if s.maxIncidentId == 0 { s.createHistoricIncidents() } + s.Search.Copy() slog.Infoln("RestoreState done in", time.Since(start)) return nil diff --git a/cmd/bosun/sched/metadata_test.go b/cmd/bosun/sched/metadata_test.go new file mode 100644 index 0000000000..1829e4b533 --- /dev/null +++ b/cmd/bosun/sched/metadata_test.go @@ -0,0 +1,57 @@ +package sched + +import ( + "fmt" + "testing" + "time" + + "bosun.org/metadata" + "bosun.org/opentsdb" +) + +var bm_sched_50k *Schedule +var bm_sched_2m *Schedule + +func init() { + bm_sched_50k = &Schedule{} + val := &Metavalue{Time: time.Now(), Value: "foo"} + + bm_sched_50k.Metadata = map[metadata.Metakey]*Metavalue{ + {Tags: "host=host1", Name: "foo"}: val, + } + bm_sched_50k.metricMetadata = map[string]*MetadataMetric{} + for i := 0; i < 50000; i++ { + key := metadata.Metakey{Name: "foo"} + key.Tags = fmt.Sprintf("host=host%d,somethingElse=aaa", i) + bm_sched_50k.Metadata[key] = val + + bm_sched_50k.metricMetadata[fmt.Sprintf("m%d", i)] = &MetadataMetric{Description: "aaa"} + } + bm_sched_2m = &Schedule{} + bm_sched_2m.Metadata = map[metadata.Metakey]*Metavalue{ + {Tags: "host=host1", Name: "foo"}: val, + } + for i := 0; i < 2000000; i++ { + key := metadata.Metakey{Name: "foo"} + key.Tags = fmt.Sprintf("host=host%d,somethingElse=aaa", i) + bm_sched_2m.Metadata[key] = val + } +} + +func BenchmarkMetadataGet50K(b *testing.B) { + for i := 0; i < b.N; i++ { + bm_sched_50k.GetMetadata("", opentsdb.TagSet{"host": "host1"}) + } +} + +func BenchmarkMetadataGet2M(b *testing.B) { + for i := 0; i < b.N; i++ { + bm_sched_2m.GetMetadata("", opentsdb.TagSet{"host": "host1"}) + } +} + +func BenchmarkMetricMetadata(b *testing.B) { + for i := 0; i < b.N; i++ { + + } +} diff --git a/cmd/bosun/sched/sched.go b/cmd/bosun/sched/sched.go index 1196d6d52b..5aefd4e4c4 100644 --- a/cmd/bosun/sched/sched.go +++ b/cmd/bosun/sched/sched.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net" + "reflect" "sort" "sync" "time" @@ -35,11 +36,16 @@ type Schedule struct { mutexAquired time.Time mutexWaitTime int64 - Conf *conf.Conf - status States - Silence map[string]*Silence - Group map[time.Time]expr.AlertKeys - Metadata map[metadata.Metakey]*Metavalue + Conf *conf.Conf + status States + Silence map[string]*Silence + Group map[time.Time]expr.AlertKeys + + // Key/value data associated with a metric, tagset, and an additional name + Metadata map[metadata.Metakey]*Metavalue + // Core metric data, including desc, type, and unit + metricMetadata map[string]*MetadataMetric + Incidents map[uint64]*Incident Search *search.Search @@ -52,16 +58,39 @@ type Schedule struct { //unknown states that need to be notified about. Collected and sent in batches. pendingUnknowns map[*conf.Notification][]*State - metalock sync.Mutex - maxIncidentId uint64 - incidentLock sync.Mutex - db *bolt.DB + metaLock sync.Mutex + metricMetaLock sync.Mutex + maxIncidentId uint64 + incidentLock sync.Mutex + db *bolt.DB LastCheck time.Time ctx *checkContext } +func (s *Schedule) Init(c *conf.Conf) error { + var err error + s.Conf = c + s.Silence = make(map[string]*Silence) + s.Group = make(map[time.Time]expr.AlertKeys) + s.Metadata = make(map[metadata.Metakey]*Metavalue) + s.metricMetadata = make(map[string]*MetadataMetric) + s.Incidents = make(map[uint64]*Incident) + s.pendingUnknowns = make(map[*conf.Notification][]*State) + s.status = make(States) + s.Search = search.NewSearch() + s.LastCheck = time.Now() + s.ctx = &checkContext{time.Now(), cache.New(0)} + if c.StateFile != "" { + s.db, err = bolt.Open(c.StateFile, 0600, nil) + if err != nil { + return err + } + } + return nil +} + type checkContext struct { runTime time.Time checkCache *cache.Cache @@ -105,83 +134,113 @@ type Metavalue struct { } func (s *Schedule) PutMetadata(k metadata.Metakey, v interface{}) { - s.metalock.Lock() - s.Metadata[k] = &Metavalue{time.Now().UTC(), v} - s.metalock.Unlock() + + isCoreMeta := (k.Name == "desc" || k.Name == "unit" || k.Name == "rate") + if !isCoreMeta { + s.metaLock.Lock() + s.Metadata[k] = &Metavalue{time.Now().UTC(), v} + s.metaLock.Unlock() + return + } + if k.Metric == "" { + slog.Error("desc, rate, and unit require metric name") + return + } + strVal, ok := v.(string) + if !ok { + slog.Errorf("desc, rate, and unit require value to be string. Found: %s", reflect.TypeOf(v)) + return + } + s.metricMetaLock.Lock() + metricData, ok := s.metricMetadata[k.Metric] + if !ok { + metricData = &MetadataMetric{} + s.metricMetadata[k.Metric] = metricData + } + switch k.Name { + case "desc": + metricData.Description = strVal + case "unit": + metricData.Unit = strVal + case "rate": + metricData.Type = strVal + } + s.metricMetaLock.Unlock() } type MetadataMetric struct { Unit string `json:",omitempty"` Type string `json:",omitempty"` - Description []*MetadataDescription -} - -type MetadataDescription struct { - Tags opentsdb.TagSet `json:",omitempty"` - Text string + Description string } func (s *Schedule) MetadataMetrics(metric string) map[string]*MetadataMetric { - s.metalock.Lock() + s.metricMetaLock.Lock() + defer s.metricMetaLock.Unlock() m := make(map[string]*MetadataMetric) - for k, mv := range s.Metadata { - tags := k.TagSet() - delete(tags, "host") - if k.Metric == "" || (metric != "" && k.Metric != metric) { - continue - } - val, _ := mv.Value.(string) - if val == "" { - continue - } - if m[k.Metric] == nil { - m[k.Metric] = &MetadataMetric{ - Description: make([]*MetadataDescription, 0), + if metric != "" { + if v, ok := s.metricMetadata[metric]; ok { + m[metric] = &MetadataMetric{ + Unit: v.Unit, + Type: v.Type, + Description: v.Description, } } - e := m[k.Metric] - Switch: - switch k.Name { - case "unit": - e.Unit = val - case "rate": - e.Type = val - case "desc": - for _, v := range e.Description { - if v.Text == val { - v.Tags = v.Tags.Intersection(tags) - break Switch - } + } else { + for k, v := range s.metricMetadata { + m[k] = &MetadataMetric{ + Unit: v.Unit, + Type: v.Type, + Description: v.Description, } - e.Description = append(e.Description, &MetadataDescription{ - Text: val, - Tags: tags, - }) } } - s.metalock.Unlock() return m } func (s *Schedule) GetMetadata(metric string, subset opentsdb.TagSet) []metadata.Metasend { - s.metalock.Lock() ms := make([]metadata.Metasend, 0) - for k, mv := range s.Metadata { - if metric != "" && k.Metric != metric { - continue + if metric != "" { + if meta, ok := s.MetadataMetrics(metric)[metric]; ok { + if meta.Description != "" { + ms = append(ms, metadata.Metasend{ + Metric: metric, + Name: "desc", + Value: meta.Description, + }) + } + if meta.Unit != "" { + ms = append(ms, metadata.Metasend{ + Metric: metric, + Name: "unit", + Value: meta.Unit, + }) + } + if meta.Type != "" { + ms = append(ms, metadata.Metasend{ + Metric: metric, + Name: "rate", + Value: meta.Type, + }) + } } - if !k.TagSet().Subset(subset) { - continue + } else { + s.metaLock.Lock() + + for k, mv := range s.Metadata { + if !k.TagSet().Subset(subset) { + continue + } + ms = append(ms, metadata.Metasend{ + Metric: k.Metric, + Tags: k.TagSet(), + Name: k.Name, + Value: mv.Value, + Time: &mv.Time, + }) } - ms = append(ms, metadata.Metasend{ - Metric: k.Metric, - Tags: k.TagSet(), - Name: k.Name, - Value: mv.Value, - Time: &mv.Time, - }) + s.metaLock.Unlock() } - s.metalock.Unlock() return ms } @@ -450,27 +509,6 @@ func Run() error { return DefaultSched.Run() } -func (s *Schedule) Init(c *conf.Conf) error { - var err error - s.Conf = c - s.Silence = make(map[string]*Silence) - s.Group = make(map[time.Time]expr.AlertKeys) - s.Metadata = make(map[metadata.Metakey]*Metavalue) - s.Incidents = make(map[uint64]*Incident) - s.pendingUnknowns = make(map[*conf.Notification][]*State) - s.status = make(States) - s.Search = search.NewSearch() - s.LastCheck = time.Now() - s.ctx = &checkContext{time.Now(), cache.New(0)} - if c.StateFile != "" { - s.db, err = bolt.Open(c.StateFile, 0600, nil) - if err != nil { - return err - } - } - return nil -} - func (s *Schedule) Load(c *conf.Conf) error { if err := s.Init(c); err != nil { return err @@ -943,7 +981,7 @@ func (s *Schedule) Host(filter string) map[string]*HostData { for _, h := range s.Search.TagValuesByTagKey("host", time.Hour*7*24) { hosts[h] = struct{}{} } - s.metalock.Lock() + s.metaLock.Lock() res := make(map[string]*HostData) for k, mv := range s.Metadata { tags := k.TagSet() @@ -1046,7 +1084,7 @@ func (s *Schedule) Host(filter string) map[string]*HostData { } } } - s.metalock.Unlock() + s.metaLock.Unlock() return res } diff --git a/cmd/bosun/web/chart.go b/cmd/bosun/web/chart.go index bffe42a03a..af1d2ad2a3 100644 --- a/cmd/bosun/web/chart.go +++ b/cmd/bosun/web/chart.go @@ -75,35 +75,31 @@ func Graph(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interf m_units := make(map[string]string) for i, q := range oreq.Queries { if ar[i] { - ms := schedule.GetMetadata(q.Metric, nil) - found := false - for _, m := range ms { - if m.Name == "unit" { - if v, ok := m.Value.(string); ok { - m_units[q.Metric] = v - } - } - if m.Name == "rate" { - found = true - switch m.Value { - case metadata.Gauge: - // ignore - case metadata.Rate: - q.Rate = true - case metadata.Counter: - q.Rate = true - q.RateOptions = opentsdb.RateOptions{ - Counter: true, - ResetValue: 1, - } - default: - return nil, fmt.Errorf("unknown metadata rate: %s", m.Value) - } - } - } + + meta := schedule.MetadataMetrics(q.Metric) + data, found := meta[q.Metric] if !found { return nil, fmt.Errorf("no metadata for %s: cannot use auto rate", q) } + if data.Unit != "" { + m_units[q.Metric] = data.Unit + } + if data.Type != "" { + switch data.Type { + case metadata.Gauge: + // ignore + case metadata.Rate: + q.Rate = true + case metadata.Counter: + q.Rate = true + q.RateOptions = opentsdb.RateOptions{ + Counter: true, + ResetValue: 1, + } + default: + return nil, fmt.Errorf("unknown metadata rate: %s", data.Type) + } + } } queries[i] = fmt.Sprintf(`q("%v", "%v", "%v")`, q, start, end) if err := schedule.Search.Expand(q); err != nil { diff --git a/cmd/bosun/web/static.go b/cmd/bosun/web/static.go index 127ea48d3a..9430c4627b 100644 --- a/cmd/bosun/web/static.go +++ b/cmd/bosun/web/static.go @@ -9098,7 +9098,7 @@ xitlUKAxazlo8/8JAAD//4NB/DtLfAAA "/js/bosun.js": { local: "web/static/js/bosun.js", - size: 97399, + size: 97224, modtime: 0, compressed: ` H4sIAAAJbogA/+y9a3vbRpIw+nnzK2COY4IxBUpKnMnIln0c27nsxJls7GRmVtZqQAIkYYEADYC6jK3/ @@ -9332,94 +9332,94 @@ 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 +8v8GAAD///9o4BnIewEA `, }, @@ -10722,68 +10722,67 @@ i3ThsRDh5j3O758AAAD///guZLQlCQAA "/js/graph.ts": { local: "web/static/js/graph.ts", - size: 10978, + size: 10883, modtime: 0, compressed: ` -H4sIAAAJbogA/7Ra/3PbtpL/WfkrEF3GJMeyZHd6/cGK4smlaZu7c9LYbmbeuH4ZiIQkxvxmEpSttv7f -3+4CIABKctM3fZmJBS4Wi8VisftZkHHGm4Zd8eWlkOz3Z4NryZe3p6yRdVosb0xj+uzx2bOO9dN+xusb -h/WCS/GhkmlZNDgiLttCivqUzcsyE7yYdqRz/nDKijafixqItWiE/MSzVlhiJ/NjK+oNSuPLZS2WXJa1 -VXKQC2jFDqEGFdwJa6vSqasfdMF6gKYs0Yn6rKi82AApKe+LhudVJpwZksZ7kGnu9Yo6XXOZrl1iDBPK -uo1B9/DujKRHuKSBXKXN2C6MzdgdOzhgdy7tjz9Y0LR5MDX8SlHLq5+RzzLhui0LPQHDgmeN8HjMdnms -hggjCnHfM9sgXbCQmJ/fje1y1YIGkwmb8/iWxWVetTCpZWCLuszZHTKhhOedCnqk0snhn7FgyduloEUN -HpkA3RkO7Ss/1l71hCDN4Ynay4yiNeczn3ubWVvNoeE+8FaWJOHR2DpxTJw0vc1SXuQy0LPPhI5pWehJ -70/nwooRDtP3TRh5z516RAetNBMtq7Opnhcm0I+Ru+zuMIAWLvshC44C+Ktp0502cwcHxjRaD0c5GrLm -Ncv5g5lmx05D/Oh7+lOu3XNiZZvmPpXxyqy878cxB/2HKGZ4av1EzwVnmaYaDOa14LdTO0Dr9+SYfYv6 -KqZzskuu1r+DzcZSYDvZrSSdqh0qdiZ0htAu2QAv7lrRUN5oJK+lE+NEkThPwFanAiIlhW9MEl4QdKIf -yUGnOFkd8WVp/V2LgC4aDopUdVsYH1lAZAzRUVJgOJ7Cz0vmDhtnoljKFXQcHuot5cWyzXg9hqFvebwK -Xfbr9GYE8kbsNmKzVzo0oPi7lMK18UXLruzUOdGmEuWCrU0I0pZW9tCm1nHP8gwgW2QCTH+XXt9qgSpk -+LtmpOms9neJK+dfRCw9aR+INL4VmyZcR9qGbAYW/uuzEOkxcp0IDbqsebW6EAtw1JXOs89S9OwFjwV7 -9yN2X8ZlJZh4kOBUDSuW43eKAiqkRSJc7NDWmeuEde3BA3CYAloO5Z7XPYpKn40LaBAarBU2+GSeXajQ -lLUUCUKFW2cccaKDbBzpNpH7M+CR+1waYOJ0JM2+IT8KCRr83/9szjXoCT1jkOOuyzQB1o9Ki9Clwdjm -dfMRZIayWwwxdMqe81vxM695Djwd1BuxtWn63EqfT43L7E4IO9xm0mCpLiK4a+16G91peTefKzd6PB1u -XifJFZ/3FgyB8J0yUJjuMhImaQRzDlg0bmlJl3TAryDPNT3xSQt7CNv3OeeVUZwXaU4AVHHqtcmy8ikx -L17D5Kfs90flgFw3N58zPofU6VkpTwvH4XMPOpOlrnwIWpR1zrP0NxcHw+Gbl01bvCkLWZdZJmrMKKYd -BnTs3sg6C0bsOnjR4HGDZvBiJWVFjayMabn0UJeA7aiFCAAeoL1oixgZQjX61D3LI0aCTuks/wStS1Gv -0xjpRq7q+3/9ZPtpKuqk1vjdBf7Yfq2AGn6lHnQvxSyljQOoKaMMAVEPR2wItqUf/oA/fL3En0Ss8ee3 -NO+4csOYFsiL26IFe+eVRCPf3ybejRMkHb0WWVUSH1nIMdJwxRms/QzGkXdh+G0EryFhzazhx4pEoAg5 -vkCnIo2/NCWeAcwMmjL/7luVCZALljx3O1SWRhm1xgkzkHbG/vfyw/txxetGhF8idqrgmOKwulIsgwHE -966QoVVwxRtQL0K4e2wHUIw26EDT3MDc69IxxVD7YEBrbBI8gIG7EUs7MOALgfSvUSUFqPCOlh45emhQ -Y6TSs+2G4OV0wpPtUkHJboF+fg4AieBZ4LiGilaWtyMAMyJJh7eLCZbbITn8zl6DM0bu4uEZRh/aXrXf -7gj+4I8gsHpoe62HbEVPdFIYOWyGp3B8hiNs59jOVXuF7ZVqJ9hOVPse2/eqXWD7XLU32N5g27h+2lyI -DKaZ/DP8NTmMwl/vI0ScLybQb4IX+GV2Vb6eNyHkwAuxfPtQvX0Q8eu65rpkr4Vs64LlZS7ASaNxK2P4 -27RzWfNYhuS+P2Qll2F+fXITjbyVXufX39zcROh1kCdMLdZNDvNelaBB2Nh8inNOJu9LSZRY0gGBHgZw -U64EoxJMyRuzHwAViweqs0bsSwsncPjN8cm3Q3afZhmbC7aGHU90iYUOqpZBiwCf10+JWHDI3D+QUFAW -6vb35b2qmvTyZU/zy3teYfDta04Q1ZSRu03n2IIwI1V/oBpt11iA9cOGOlFW7ouyu9UN1z3WlNrI2iWd -fI4Vj8lZntua09styqXTRN457rMB1Z9ToZO905nI58cYU8BsBZ9x1UI47IIPTeVEHg163Nkc9LNj2tQX -0EOZnhwfbjqyKBZfU7+JjAgNp2Y7jah+GKUB+gLLbPFz9ag32s9k16rvxi2V1Yab7SecMV4KGQYTXqUT -TAUTvJ3QUnEEnNY4Fk0TditLOEIwg7kit/7bp/W0Y9EXM/5NzGBgLuY+635KwYPdlSvOv6NiVfLRx5Ch -Kzp90deJZw59P0cXRLZos4yqB9qa+9EOeq57dg1TdzaW34D/MIE0iQaJnKpvK7uqSbHC5hnU2GLjVNk0 -NdB7swITzgsd3sSPkbGuNiv+KNJkwrCAYU1bC7YqIfilDePZPd80FCkXaQ004B8T+w7IYD3YLYTJktM/ -GUO0MOQjNu+vjVOCRYUCu0Ydpo5OzOrs9eb8Cf4Tfxc0FXwHsFIm3pQ5ZAcRzs1mqN9H5fVUHFufp0cz -gQldSMPd/qXg8wySS8kWAi84uiIZjxJxTW153z9zWNCgw06AdKZGzvYdQeTzb1wsaHXuo/oOhcNG6Bzb -fjQ2gWtm4s7BAXKO3/NcYR2637WmtRM6h8j3tr1RSFO2zQx/rWo968Kvtd6jBl07o79z9eLlEoMcdlge -FAtQka8Mcgapqf3VkYaCKQl5wmf+PZfBNTu4oQsjznXDrjSzHdXXFNVv8faZWk+llr8U9SkW02n2N9/N -cde3N46p/tMHzAVaYANdOoUGj5pSyy+rBl7tYbOYKUUGTvlhe1UtsnXcfOM6tX6l10fpoxp7qdvJzOo4 -mXxqC6dKmdjJoiquE7UL8b3MunWPS6mlU0ld4ioNKCTg+e8oWqrawPW0f1M56NWBCmzdRcZ1u5Bbd4a2 -OE+9qtyCeVQQA9nduq8xMlWglFq7SGKwsQcApt0G5PssvzcfQ4IboemdnEUwG1/2WhzgiNOBUd8B1/ZC -XJ03si2OddKDtttYX99TxuhfPgTz774NIHfKkod0XaBOZbrYhHUU7RmjamMY5tfOZ6wFZRZpIRJ2asrm -PSJ0yWxlmBr6TJfEIKGTtkdGV0hbKba23iuHtpIqarM5afOeQl5o6+0IxtunsSwvySwh3aK0WdYJokJ7 -ryAsy60g/rBb0PbKYE5YE+qxp58/YD9X4E9d1YEFofpNTIay4ann/uSw/dOmsp5baNsbg+5mpL/bwYFq -EsZ4EQb/Fa8gxAXR+D5N5IqWSOjVjaEKI8BfHvqfEOzONx2i0WHaRTWiiMtE/HLxDtFXWWBpq8Ogn3Yo -3fTOmQszqdvJ+yfd8bM5mjvQg0oC/ejFMScXmRzUTYov4cpMAF5chsO3lIxglRLvE8wa8TLBIKehj9pQ -di8ZhUV5YV6y6NtmbUJ9MYugKYY63H3v05Xzz7vRPqjQ720wUer+wL0ewP3W7yxVXeJYqKu0YBn9Mmu3 -u7kVFyrV44Kyy3vlP9Ov9832dMoczrQr4hO5RmojsO9zO6bQZnaX+VXhITiAxj5H3Bk7ugPx9aEDJuEP -fzaJF1fsJOa4qlcjYdS/0bhSnz4oZtf+CEzwKxPnIsjpiiKbEns3WQ7XnjutqeNrnhIHidk7aS6mgn/A -v6Pz86Pvvw/2DKSdR3/vjfzpp9M8D6J9dxN0JM5wCKa/febdmRPVCiEzwhgdCA/tsTgk1zmkyPxkDOqS -Hr6p0zFlfEk+Oe2C1HOPqx+V9AtVNN77EvAn8jT6ssD70GbHgMCFFI5V1Wt/UuZj62izHR2C6U50Hdjb -mdZ718HnzS91ZpA99rV4dwuWDY9H0CYY82ERBmcB2RZ+DStustkm+9Ljz+zv6dfWeP/cbtcMW3F6u2LU -BcFeG2h5ADB4lm3CcFsYvoc0K3cqTo14un11AzXaTsdxI5GCPkKaaMoeR+y/j4+PzW2DX9QiH2EvIDze -YNlHbyBfV9U4SWtIe/jBTyCbn8uqxVeMncYaYSsMoC69IZa8DfBaX79aJFXp1X8wI/oj/pEirzJ6/Rq8 -nLdSQoaiz1Zmw7ksAFsWRzoKDMm3jlYyz2ZDXIsmwOhYYLiAESWMz4evBETD5OVEiXtFc2VpAdVqqFXh -BSBoQMO5bnIp60Z/4ufCdq3QDBGKagdqnLKe2osX9xyKwZBkoLeAVWiVnjR1KIH+BD6nqCge6MOal2lR -tZLhJyqwWiAOWVm8ydL4Fh7VB2IZbAe4xhAyJE/KItvMhqY1xLuTFkYeZHLK2QrcZXZw15ZyigcBPRpO -iSIcLOE/cKX5kjV1vINtXBXLGfz3+SccW8NX+qgp64yrsirXkIn0GvFtNezMKS1rpJYauVWGetMDD/8K -AAD//xBJ36LiKgAA +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 `, }, @@ -13099,49 +13098,49 @@ Uhr/hqCpi6vxtEkkNGeXvv7evyt3gxtB2PWr+aehzZ8iC+Fx8Oxr1ntYckni4XGeOL6ahyePVwAAAP// "/partials/graph.html": { local: "web/static/partials/graph.html", - size: 8201, + size: 8107, modtime: 0, compressed: ` -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 +qx8AAA== `, }, diff --git a/cmd/bosun/web/static/js/bosun.js b/cmd/bosun/web/static/js/bosun.js index a51f78a08a..7500c7aada 100644 --- a/cmd/bosun/web/static/js/bosun.js +++ b/cmd/bosun/web/static/js/bosun.js @@ -1767,14 +1767,9 @@ bosunControllers.controller('GraphCtrl', ['$scope', '$http', '$location', '$rout .error(function (error) { $scope.error = 'Unable to fetch metrics: ' + error; }); - $http.get('/api/metadata/get?metric=' + metric) + $http.get('/api/metadata/metrics?metric=' + metric) .success(function (data) { - var canAuto = false; - angular.forEach(data, function (val) { - if (val.Metric == metric && val.Name == 'rate') { - canAuto = true; - } - }); + var canAuto = data[metric] && data[metric].Type; $scope.canAuto[metric] = canAuto; }) .error(function (err) { diff --git a/cmd/bosun/web/static/js/graph.ts b/cmd/bosun/web/static/js/graph.ts index 13493c9c24..410f3aa6c3 100644 --- a/cmd/bosun/web/static/js/graph.ts +++ b/cmd/bosun/web/static/js/graph.ts @@ -264,14 +264,9 @@ bosunControllers.controller('GraphCtrl', ['$scope', '$http', '$location', '$rout .error(function(error) { $scope.error = 'Unable to fetch metrics: ' + error; }); - $http.get('/api/metadata/get?metric=' + metric) + $http.get('/api/metadata/metrics?metric=' + metric) .success(data => { - var canAuto = false; - angular.forEach(data, val => { - if (val.Metric == metric && val.Name == 'rate') { - canAuto = true; - } - }); + var canAuto = data[metric] && data[metric].Type; $scope.canAuto[metric] = canAuto; }) .error(err => { diff --git a/cmd/bosun/web/static/partials/graph.html b/cmd/bosun/web/static/partials/graph.html index a8c5e5e14d..c0ab901215 100644 --- a/cmd/bosun/web/static/partials/graph.html +++ b/cmd/bosun/web/static/partials/graph.html @@ -171,12 +171,10 @@