// insert inserts the alert into the aggregation group. If the aggregation group // is empty afterwards, it returns true. func (ag *aggrGroup) insert(alert *types.Alert) { ag.mtx.Lock() defer ag.mtx.Unlock() ag.alerts[alert.Fingerprint()] = alert // Immediately trigger a flush if the wait duration for this // alert is already over. if !ag.hasSent && alert.StartsAt.Add(ag.opts.GroupWait).Before(time.Now()) { ag.next.Reset(0) } }
// Put adds the given alert to the set. func (a *Alerts) Put(alerts ...*types.Alert) error { a.mtx.Lock() defer a.mtx.Unlock() err := a.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bktAlerts) for _, alert := range alerts { fp := make([]byte, 8) binary.BigEndian.PutUint64(fp, uint64(alert.Fingerprint())) ab := b.Get(fp) // Merge the alert with the existing one. if ab != nil { var old types.Alert if err := json.Unmarshal(ab, &old); err != nil { return fmt.Errorf("decoding alert failed: %s", err) } // Merge alerts if there is an overlap in activity range. if (alert.EndsAt.After(old.StartsAt) && alert.EndsAt.Before(old.EndsAt)) || (alert.StartsAt.After(old.StartsAt) && alert.StartsAt.Before(old.EndsAt)) { alert = old.Merge(alert) } } ab, err := json.Marshal(alert) if err != nil { return fmt.Errorf("encoding alert failed: %s", err) } if err := b.Put(fp, ab); err != nil { return fmt.Errorf("writing alert failed: %s", err) } // Send the update to all subscribers. for _, ch := range a.listeners { ch <- alert } } return nil }) return err }
// hasUpdates checks an alert against the last notification that was made // about it. func (n *DedupingNotifier) hasUpdate(alert *types.Alert, last *types.NotifyInfo, now time.Time, interval time.Duration) bool { if last != nil { if alert.Resolved() { if last.Resolved { return false } } else if !last.Resolved { // Do not send again if last was delivered unless // the repeat interval has already passed. if !now.After(last.Timestamp.Add(interval)) { return false } } } else if alert.Resolved() { // If the alert is resolved but we never notified about it firing, // there is nothing to do. return false } return true }
// set the alert in the source cache. func (r *InhibitRule) set(a *types.Alert) { r.mtx.Lock() defer r.mtx.Unlock() r.scache[a.Fingerprint()] = a }
// Put implements the Alerts interface. func (a *Alerts) Put(alerts ...*types.Alert) error { dbmtx.Lock() defer dbmtx.Unlock() tx, err := a.db.Begin() if err != nil { return err } // The insert invariant requires that there are no two alerts with the same // fingerprint that have overlapping activity range ([StartsAt:EndsAt]). // Such alerts are merged into a single one with the union of both intervals // as its new activity interval. // The exact merge procedure is defined on the Alert structure. Here, we just // care about finding intersecting alerts for each new inserts, deleting them // if existant, and insert the new alert we retrieved by merging. overlap, err := tx.Prepare(` SELECT id, annotations, starts_at, ends_at, updated_at, timeout FROM alerts WHERE fingerprint == $1 AND ( (starts_at <= $2 AND ends_at >= $2) OR (starts_at <= $3 AND ends_at >= $3) ) `) if err != nil { tx.Rollback() return err } defer overlap.Close() delOverlap, err := tx.Prepare(` DELETE FROM alerts WHERE id IN ( SELECT id FROM alerts WHERE fingerprint == $1 AND ( (starts_at <= $2 AND ends_at >= $2) OR (starts_at <= $3 AND ends_at >= $3) ) ) `) if err != nil { tx.Rollback() return err } defer delOverlap.Close() insert, err := tx.Prepare(` INSERT INTO alerts(fingerprint, labels, annotations, starts_at, ends_at, updated_at, timeout) VALUES ($1, $2, $3, $4, $5, $6, $7) `) if err != nil { tx.Rollback() return err } defer insert.Close() for _, alert := range alerts { fp := alert.Fingerprint() // Retrieve all intersecting alerts and delete them. olaps, err := overlap.Query(int64(fp), alert.StartsAt, alert.EndsAt) if err != nil { tx.Rollback() return err } var ( overlapIDs []int64 merges []*types.Alert ) for olaps.Next() { var ( id int64 na types.Alert ann []byte ) if err := olaps.Scan( &id, &ann, &na.StartsAt, &na.EndsAt, &na.UpdatedAt, &na.Timeout, ); err != nil { tx.Rollback() return err } if err := json.Unmarshal(ann, &na.Annotations); err != nil { tx.Rollback() return err } na.Labels = alert.Labels merges = append(merges, &na) overlapIDs = append(overlapIDs, id) } if err := olaps.Err(); err != nil { tx.Rollback() return err } // Merge them. for _, ma := range merges { alert = alert.Merge(ma) } // Delete the old ones. if _, err := delOverlap.Exec(int64(fp), alert.StartsAt, alert.EndsAt); err != nil { tx.Rollback() return err } // Insert the final alert. labels, err := json.Marshal(alert.Labels) if err != nil { tx.Rollback() return err } annotations, err := json.Marshal(alert.Annotations) if err != nil { tx.Rollback() return err } _, err = insert.Exec( int64(fp), labels, annotations, alert.StartsAt, alert.EndsAt, alert.UpdatedAt, alert.Timeout, ) if err != nil { tx.Rollback() return err } a.mtx.RLock() for _, ch := range a.listeners { ch <- alert } a.mtx.RUnlock() } tx.Commit() return nil }