2 * Copyright (c) 2020 AT&T Intellectual Property.
3 * Copyright (c) 2020 Nokia.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * This source code is part of the near-RT RIC (RAN Intelligent Controller)
18 * platform project (RICP).
34 "gerrit.o-ran-sc.org/r/ric-plt/alarm-go.git/alarm"
35 app "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
36 clientruntime "github.com/go-openapi/runtime/client"
37 "github.com/go-openapi/strfmt"
38 "github.com/prometheus/alertmanager/api/v2/client"
39 "github.com/prometheus/alertmanager/api/v2/client/alert"
40 "github.com/prometheus/alertmanager/api/v2/models"
41 "github.com/spf13/viper"
44 func (a *AlarmManager) ClearExpiredAlarms(m AlarmNotification, idx int, mLocked bool) bool {
45 d, ok := alarm.RICAlarmDefinitions[m.Alarm.SpecificProblem]
46 if !ok || d.TimeToLive == 0 {
50 elapsed := (time.Now().UnixNano() - m.AlarmTime) / 1e9
51 if int(elapsed) >= d.TimeToLive {
52 app.Logger.Info("Alarm (sp=%d id=%d) with TTL=%d expired, clearing ...", m.Alarm.SpecificProblem, m.AlarmId, d.TimeToLive)
54 m.AlarmAction = alarm.AlarmActionClear
55 m.AlarmTime = time.Now().UnixNano()
57 if !mLocked { // For testing purpose
60 a.ProcessClearAlarm(&m, d, idx)
66 func (a *AlarmManager) StartTTLTimer(interval int) {
67 tick := time.Tick(time.Duration(interval) * time.Second)
70 for idx, m := range a.activeAlarms {
71 if a.ClearExpiredAlarms(m, idx, true) {
72 a.mutex.Lock() // ClearExpiredAlarms unlocks the mutex, so re-lock here
80 func (a *AlarmManager) StartAlertTimer() {
81 tick := time.Tick(time.Duration(a.alertInterval) * time.Millisecond)
86 for _, m := range a.activeAlarms {
87 app.Logger.Info("Re-raising alarm: %v", m)
88 a.PostAlert(a.GenerateAlertLabels(m.AlarmId, m.Alarm, AlertStatusActive, m.AlarmTime))
94 func (a *AlarmManager) Consume(rp *app.RMRParams) (err error) {
95 app.Logger.Info("Message received!")
97 defer app.Rmr.Free(rp.Mbuf)
99 case alarm.RIC_ALARM_UPDATE:
102 app.Logger.Info("Unknown Message Type '%d', discarding", rp.Mtype)
108 func (a *AlarmManager) HandleAlarms(rp *app.RMRParams) (*alert.PostAlertsOK, error) {
109 var m alarm.AlarmMessage
110 app.Logger.Info("Received JSON: %s", rp.Payload)
111 if err := json.Unmarshal(rp.Payload, &m); err != nil {
112 app.Logger.Error("json.Unmarshal failed: %v", err)
115 app.Logger.Info("newAlarm: %v", m)
117 return a.ProcessAlarm(&AlarmNotification{m, alarm.AlarmDefinition{}})
120 func (a *AlarmManager) ProcessAlarm(m *AlarmNotification) (*alert.PostAlertsOK, error) {
122 alarmDef := &alarm.AlarmDefinition{}
124 if alarmDef, ok = alarm.RICAlarmDefinitions[m.Alarm.SpecificProblem]; !ok {
125 app.Logger.Warn("Alarm (SP='%d') not recognized, suppressing ...", m.Alarm.SpecificProblem)
130 idx, found := a.IsMatchFound(m.Alarm)
131 // Suppress duplicate alarms
132 if found && m.AlarmAction == alarm.AlarmActionRaise {
133 app.Logger.Info("Duplicate alarm found, suppressing ...")
134 if m.PerceivedSeverity == a.activeAlarms[idx].PerceivedSeverity {
135 // Duplicate with same severity found
139 // Remove duplicate with different severity
140 a.activeAlarms = a.RemoveAlarm(a.activeAlarms, idx, "active")
144 // Clear alarm if found from active alarm list
145 if found && m.AlarmAction == alarm.AlarmActionClear {
146 return a.ProcessClearAlarm(m, alarmDef, idx)
149 // New alarm -> update active alarms and post to Alert Manager
150 if m.AlarmAction == alarm.AlarmActionRaise {
151 return a.ProcessRaiseAlarm(m, alarmDef)
158 func (a *AlarmManager) ProcessRaiseAlarm(m *AlarmNotification, alarmDef *alarm.AlarmDefinition) (*alert.PostAlertsOK, error) {
159 app.Logger.Debug("Raise alarmDef.RaiseDelay = %v, AlarmNotification = %v", alarmDef.RaiseDelay, *m)
161 // RaiseDelay > 0 in an alarm object in active alarm table indicates that raise delay is still ongoing for the alarm
162 m.AlarmDefinition.RaiseDelay = alarmDef.RaiseDelay
163 a.UpdateAlarmFields(a.GenerateAlarmId(), m)
164 a.UpdateActiveAlarmList(m)
167 if alarmDef.RaiseDelay > 0 {
168 timerDelay(alarmDef.RaiseDelay)
170 // Alarm may have been deleted from active alarms table during delay or table index may have changed
171 idx, found := a.IsMatchFound(m.Alarm)
173 // Alarm is not showed in active alarms or alarm history via CLI before RaiseDelay has elapsed, i.e the value is 0
174 a.activeAlarms[idx].AlarmDefinition.RaiseDelay = 0
175 app.Logger.Debug("Raise after delay alarmDef.RaiseDelay = %v, AlarmNotification = %v", alarmDef.RaiseDelay, *m)
178 app.Logger.Debug("Alarm deleted during raise delay. AlarmNotification = %v", *m)
184 m.AlarmDefinition.RaiseDelay = 0
185 a.UpdateAlarmHistoryList(m)
186 a.WriteAlarmInfoToPersistentVolume()
188 // Send alarm notification to NOMA, if enabled
189 if app.Config.GetBool("controls.noma.enabled") {
190 return a.PostAlarm(m)
192 return a.PostAlert(a.GenerateAlertLabels(m.AlarmId, m.Alarm, AlertStatusActive, m.AlarmTime))
195 func (a *AlarmManager) ProcessClearAlarm(m *AlarmNotification, alarmDef *alarm.AlarmDefinition, idx int) (*alert.PostAlertsOK, error) {
196 app.Logger.Debug("Clear alarmDef.ClearDelay = %v, AlarmNotification = %v", alarmDef.ClearDelay, *m)
197 if alarmDef.ClearDelay > 0 {
199 timerDelay(alarmDef.ClearDelay)
200 app.Logger.Debug("Clear after delay alarmDef.ClearDelay = %v, AlarmNotification = %v", alarmDef.ClearDelay, *m)
202 // Another alarm clear may have happened during delay and active alarms table index changed
204 idx, found = a.IsMatchFound(m.Alarm)
210 a.UpdateAlarmFields(a.activeAlarms[idx].AlarmId, m)
211 a.alarmHistory = append(a.alarmHistory, *m)
212 a.activeAlarms = a.RemoveAlarm(a.activeAlarms, idx, "active")
213 if (len(a.alarmHistory) >= a.maxAlarmHistory) && (a.exceededAlarmHistoryOn == false) {
214 app.Logger.Warn("alarm history count exceeded maxAlarmHistory threshold")
215 a.GenerateThresholdAlarm(alarm.ALARM_HISTORY_EXCEED_MAX_THRESHOLD, "history")
218 if a.exceededActiveAlarmOn && m.Alarm.SpecificProblem == alarm.ACTIVE_ALARM_EXCEED_MAX_THRESHOLD {
219 a.exceededActiveAlarmOn = false
222 if a.exceededAlarmHistoryOn && m.Alarm.SpecificProblem == alarm.ALARM_HISTORY_EXCEED_MAX_THRESHOLD {
223 a.exceededAlarmHistoryOn = false
225 a.WriteAlarmInfoToPersistentVolume()
228 if a.postClear && app.Config.GetBool("controls.noma.enabled") {
229 m.PerceivedSeverity = alarm.SeverityCleared
230 return a.PostAlarm(m)
235 func timerDelay(delay int) {
236 timer := time.NewTimer(time.Duration(delay) * time.Second)
240 func (a *AlarmManager) IsMatchFound(newAlarm alarm.Alarm) (int, bool) {
241 for i, m := range a.activeAlarms {
242 if m.ManagedObjectId == newAlarm.ManagedObjectId && m.ApplicationId == newAlarm.ApplicationId &&
243 m.SpecificProblem == newAlarm.SpecificProblem && m.IdentifyingInfo == newAlarm.IdentifyingInfo {
250 func (a *AlarmManager) RemoveAlarm(alarms []AlarmNotification, i int, listName string) []AlarmNotification {
251 app.Logger.Info("Alarm '%+v' deleted from the '%s' list", alarms[i], listName)
252 copy(alarms[i:], alarms[i+1:])
253 return alarms[:len(alarms)-1]
256 func (a *AlarmManager) GenerateAlarmId() int {
257 a.uniqueAlarmId++ // @todo: generate a unique ID
258 return a.uniqueAlarmId
261 func (a *AlarmManager) UpdateAlarmFields(alarmId int, newAlarm *AlarmNotification) {
262 alarmDef := alarm.RICAlarmDefinitions[newAlarm.SpecificProblem]
263 newAlarm.AlarmId = alarmId
264 newAlarm.AlarmText = alarmDef.AlarmText
265 newAlarm.EventType = alarmDef.EventType
268 func (a *AlarmManager) GenerateThresholdAlarm(sp int, data string) bool {
269 thresholdAlarm := a.alarmClient.NewAlarm(sp, alarm.SeverityWarning, "threshold", data)
270 thresholdMessage := alarm.AlarmMessage{
271 Alarm: thresholdAlarm,
272 AlarmAction: alarm.AlarmActionRaise,
273 AlarmTime: time.Now().UnixNano(),
275 alarmDef := alarm.RICAlarmDefinitions[sp]
276 alarmId := a.GenerateAlarmId()
277 alarmDef.AlarmId = alarmId
278 a.activeAlarms = append(a.activeAlarms, AlarmNotification{thresholdMessage, *alarmDef})
279 a.alarmHistory = append(a.alarmHistory, AlarmNotification{thresholdMessage, *alarmDef})
284 func (a *AlarmManager) UpdateActiveAlarmList(newAlarm *AlarmNotification) {
285 /* If maximum number of active alarms is reached, an error log writing is made, and new alarm indicating the problem is raised.
286 The attempt to raise the alarm next time will be suppressed when found as duplicate. */
287 if (len(a.activeAlarms) >= a.maxActiveAlarms) && (a.exceededActiveAlarmOn == false) {
288 app.Logger.Warn("active alarm count exceeded maxActiveAlarms threshold")
289 a.exceededActiveAlarmOn = a.GenerateThresholdAlarm(alarm.ACTIVE_ALARM_EXCEED_MAX_THRESHOLD, "active")
292 // @todo: For now just keep the active alarms in-memory. Use SDL later for persistence
293 a.activeAlarms = append(a.activeAlarms, *newAlarm)
296 func (a *AlarmManager) UpdateAlarmHistoryList(newAlarm *AlarmNotification) {
297 /* If maximum number of events in alarm history is reached, an error log writing is made,
298 and new alarm indicating the problem is raised. The attempt to add new event time will
301 if (len(a.alarmHistory) >= a.maxAlarmHistory) && (a.exceededAlarmHistoryOn == false) {
302 app.Logger.Warn("alarm history count exceeded maxAlarmHistory threshold")
303 a.exceededAlarmHistoryOn = a.GenerateThresholdAlarm(alarm.ALARM_HISTORY_EXCEED_MAX_THRESHOLD, "history")
306 // @todo: For now just keep the alarms history in-memory. Use SDL later for persistence
307 a.alarmHistory = append(a.alarmHistory, *newAlarm)
310 func (a *AlarmManager) PostAlarm(m *AlarmNotification) (*alert.PostAlertsOK, error) {
311 result, err := json.Marshal(m)
313 app.Logger.Info("json.Marshal failed: %v", err)
317 fullUrl := fmt.Sprintf("%s/%s", app.Config.GetString("controls.noma.host"), app.Config.GetString("controls.noma.alarmUrl"))
318 app.Logger.Info("Posting alarm to '%s'", fullUrl)
320 resp, err := http.Post(fullUrl, "application/json", bytes.NewReader(result))
321 if err != nil || resp == nil {
322 app.Logger.Info("Unable to post alarm to '%s': %v", fullUrl, err)
328 func (a *AlarmManager) GenerateAlertLabels(alarmId int, newAlarm alarm.Alarm, status AlertStatus, alarmTime int64) (models.LabelSet, models.LabelSet) {
329 if strings.Contains(newAlarm.ApplicationId, "FM") {
330 app.Logger.Info("Alarm '%d' is originated from FM, ignoring ...", alarmId)
331 return models.LabelSet{}, models.LabelSet{}
334 alarmDef := alarm.RICAlarmDefinitions[newAlarm.SpecificProblem]
335 amLabels := models.LabelSet{
336 "status": string(status),
337 "alertname": alarmDef.AlarmText,
338 "severity": string(newAlarm.PerceivedSeverity),
339 "service": fmt.Sprintf("%s/%s", newAlarm.ManagedObjectId, newAlarm.ApplicationId),
340 "info": newAlarm.IdentifyingInfo,
341 "system_name": "RIC",
343 amAnnotations := models.LabelSet{
344 "alarm_id": fmt.Sprintf("%d", alarmId),
345 "specific_problem": fmt.Sprintf("%d", newAlarm.SpecificProblem),
346 "event_type": alarmDef.EventType,
347 "identifying_info": newAlarm.IdentifyingInfo,
348 "additional_info": newAlarm.AdditionalInfo,
349 "description": fmt.Sprintf("%s:%s", newAlarm.IdentifyingInfo, newAlarm.AdditionalInfo),
350 "summary": newAlarm.IdentifyingInfo,
351 "instructions": alarmDef.OperationInstructions,
352 "timestamp": fmt.Sprintf("%s", time.Unix(0, alarmTime).Format("02/01/2006, 15:04:05")),
355 return amLabels, amAnnotations
358 func (a *AlarmManager) NewAlertmanagerClient() *client.AlertmanagerAPI {
359 cr := clientruntime.New(a.amHost, a.amBaseUrl, a.amSchemes)
360 return client.New(cr, strfmt.Default)
363 func (a *AlarmManager) PostAlert(amLabels, amAnnotations models.LabelSet) (*alert.PostAlertsOK, error) {
364 if len(amLabels) == 0 || len(amAnnotations) == 0 {
365 return &alert.PostAlertsOK{}, nil
368 pa := &models.PostableAlert{
370 GeneratorURL: strfmt.URI("http://service-ricplt-alarmmanager-http.ricplt:8080/ric/v1/alarms"),
373 Annotations: amAnnotations,
375 alertParams := alert.NewPostAlertsParams().WithAlerts(models.PostableAlerts{pa})
377 app.Logger.Info("Posting alerts: labels: %+v, annotations: %+v", amLabels, amAnnotations)
378 ok, err := a.NewAlertmanagerClient().Alert.PostAlerts(alertParams)
380 app.Logger.Error("Posting alerts to '%s/%s' failed: %v", a.amHost, a.amBaseUrl, err)
385 func (a *AlarmManager) GetAlerts() (*alert.GetAlertsOK, error) {
387 alertParams := alert.NewGetAlertsParams()
388 alertParams.Active = &active
389 resp, err := a.NewAlertmanagerClient().Alert.GetAlerts(alertParams)
391 app.Logger.Error("Getting alerts from '%s/%s' failed: %v", a.amHost, a.amBaseUrl, err)
394 app.Logger.Info("GetAlerts: %+v", resp)
399 func (a *AlarmManager) ProcessAlerts() {
400 resp, err := a.GetAlerts()
401 if err != nil || resp == nil {
402 app.Logger.Error("Getting alerts from '%s/%s' failed: %v", a.amHost, a.amBaseUrl, err)
406 var buildAlarm = func(alert *models.GettableAlert) alarm.Alarm {
407 a := alarm.Alarm{ManagedObjectId: "SEP", ApplicationId: "FM"}
409 if v, ok := alert.Alert.Labels["specific_problem"]; ok {
410 sp, _ := strconv.Atoi(v)
411 a.SpecificProblem = sp
414 if v, ok := alert.Alert.Labels["severity"]; ok {
415 a.PerceivedSeverity = alarm.Severity(fmt.Sprint(v))
418 if v, ok := alert.Alert.Labels["name"]; ok {
422 if v, ok := alert.Annotations["description"]; ok {
423 a.IdentifyingInfo = v
429 // Remove cleared alerts first
430 for _, m := range a.activeAlarms {
431 if m.ApplicationId != "FM" {
436 for _, alert := range resp.Payload {
437 v, ok := alert.Alert.Labels["service"]
438 if !ok || !strings.Contains(v, "FM") {
442 a := buildAlarm(alert)
443 if m.ManagedObjectId == a.ManagedObjectId && m.ApplicationId == a.ApplicationId &&
444 m.SpecificProblem == a.SpecificProblem && m.IdentifyingInfo == a.IdentifyingInfo {
451 m.AlarmAction = alarm.AlarmActionClear
452 go a.ProcessAlarm(&m)
456 for _, alert := range resp.Payload {
457 v, ok := alert.Alert.Labels["service"]
458 if ok && strings.Contains(v, "FM") {
459 m := alarm.AlarmMessage{Alarm: buildAlarm(alert), AlarmAction: alarm.AlarmActionRaise, AlarmTime: time.Now().UnixNano()}
460 go a.ProcessAlarm(&AlarmNotification{m, alarm.AlarmDefinition{}})
465 func (a *AlarmManager) StatusCB() bool {
467 app.Logger.Info("RMR not ready yet!")
472 func (a *AlarmManager) ConfigChangeCB(configparam string) {
473 a.maxActiveAlarms = app.Config.GetInt("controls.maxActiveAlarms")
474 if a.maxActiveAlarms == 0 {
475 a.maxActiveAlarms = 5000
478 a.maxAlarmHistory = app.Config.GetInt("controls.maxAlarmHistory")
479 if a.maxAlarmHistory == 0 {
480 a.maxAlarmHistory = 20000
483 a.alertInterval = viper.GetInt("controls.promAlertManager.alertInterval")
484 a.amHost = viper.GetString("controls.promAlertManager.address")
486 app.Logger.Debug("ConfigChangeCB: maxActiveAlarms %v", a.maxActiveAlarms)
487 app.Logger.Debug("ConfigChangeCB: maxAlarmHistory = %v", a.maxAlarmHistory)
488 app.Logger.Debug("ConfigChangeCB: alertInterval %v", a.alertInterval)
489 app.Logger.Debug("ConfigChangeCB: amHost = %v", a.amHost)
494 func (a *AlarmManager) ReadAlarmDefinitionFromJson() {
496 filename := os.Getenv("DEF_FILE")
497 file, err := ioutil.ReadFile(filename)
499 data := RicAlarmDefinitions{}
500 err = json.Unmarshal([]byte(file), &data)
502 for _, alarmDefinition := range data.AlarmDefinitions {
503 _, exists := alarm.RICAlarmDefinitions[alarmDefinition.AlarmId]
505 app.Logger.Error("ReadAlarmDefinitionFromJson: alarm definition already exists for %v", alarmDefinition.AlarmId)
507 app.Logger.Debug("ReadAlarmDefinitionFromJson: alarm %v", alarmDefinition.AlarmId)
508 ricAlarmDefintion := new(alarm.AlarmDefinition)
509 ricAlarmDefintion.AlarmId = alarmDefinition.AlarmId
510 ricAlarmDefintion.AlarmText = alarmDefinition.AlarmText
511 ricAlarmDefintion.EventType = alarmDefinition.EventType
512 ricAlarmDefintion.OperationInstructions = alarmDefinition.OperationInstructions
513 ricAlarmDefintion.RaiseDelay = alarmDefinition.RaiseDelay
514 ricAlarmDefintion.ClearDelay = alarmDefinition.ClearDelay
515 ricAlarmDefintion.TimeToLive = alarmDefinition.TimeToLive
516 alarm.RICAlarmDefinitions[alarmDefinition.AlarmId] = ricAlarmDefintion
520 app.Logger.Error("ReadAlarmDefinitionFromJson: json.Unmarshal failed with error %v", err)
523 app.Logger.Error("ReadAlarmDefinitionFromJson: ioutil.ReadFile failed with error %v", err)
527 func (a *AlarmManager) ReadAlarmInfoFromPersistentVolume() {
528 var alarmpersistentinfo AlarmPersistentInfo
529 byteValue, rerr := ioutil.ReadFile(a.alarmInfoPvFile)
531 app.Logger.Info("Unable to read alarminfo.json : %v", rerr)
533 err := json.Unmarshal(byteValue, &alarmpersistentinfo)
535 app.Logger.Error("alarmpersistentinfo json unmarshal error %v", err)
537 a.uniqueAlarmId = alarmpersistentinfo.UniqueAlarmId
538 a.activeAlarms = make([]AlarmNotification, len(alarmpersistentinfo.ActiveAlarms))
539 a.alarmHistory = make([]AlarmNotification, len(alarmpersistentinfo.AlarmHistory))
540 copy(a.activeAlarms, alarmpersistentinfo.ActiveAlarms)
541 copy(a.alarmHistory, alarmpersistentinfo.AlarmHistory)
546 func (a *AlarmManager) WriteAlarmInfoToPersistentVolume() {
547 var alarmpersistentinfo AlarmPersistentInfo
548 alarmpersistentinfo.UniqueAlarmId = a.uniqueAlarmId
549 alarmpersistentinfo.ActiveAlarms = make([]AlarmNotification, len(a.activeAlarms))
550 alarmpersistentinfo.AlarmHistory = make([]AlarmNotification, len(a.alarmHistory))
552 copy(alarmpersistentinfo.ActiveAlarms, a.activeAlarms)
553 copy(alarmpersistentinfo.AlarmHistory, a.alarmHistory)
555 wdata, err := json.MarshalIndent(alarmpersistentinfo, "", " ")
557 app.Logger.Error("alarmpersistentinfo json marshal error %v", err)
559 werr := ioutil.WriteFile(a.alarmInfoPvFile, wdata, 0777)
561 app.Logger.Error("alarminfo.json file write error %v", werr)
566 func (a *AlarmManager) Run(sdlcheck bool, ttlInterval int) {
567 app.Logger.SetMdc("alarmManager", fmt.Sprintf("%s:%s", Version, Hash))
568 app.SetReadyCB(func(d interface{}) { a.rmrReady = true }, true)
569 app.Resource.InjectStatusCb(a.StatusCB)
570 app.AddConfigChangeListener(a.ConfigChangeCB)
572 alarm.RICAlarmDefinitions = make(map[int]*alarm.AlarmDefinition)
573 a.ReadAlarmDefinitionFromJson()
577 // Start background timer for re-raising alerts
578 go a.StartAlertTimer()
579 go a.StartTTLTimer(ttlInterval)
581 a.alarmClient, _ = alarm.InitAlarm("SEP", "ALARMMANAGER")
583 a.ReadAlarmInfoFromPersistentVolume()
585 time.Sleep(8 * time.Second)
586 app.RunWithRunParams(a, app.RunParams{SdlCheck: sdlcheck, DisableAlarmClient: true})
589 func NewAlarmManager(amHost string, alertInterval int, clearAlarm bool) *AlarmManager {
590 if alertInterval == 0 {
591 alertInterval = viper.GetInt("controls.promAlertManager.alertInterval")
595 amHost = viper.GetString("controls.promAlertManager.address")
598 maxActiveAlarms := app.Config.GetInt("controls.maxActiveAlarms")
599 if maxActiveAlarms == 0 {
600 maxActiveAlarms = 5000
603 maxAlarmHistory := app.Config.GetInt("controls.maxAlarmHistory")
604 if maxAlarmHistory == 0 {
605 maxAlarmHistory = 20000
608 return &AlarmManager{
610 postClear: clearAlarm,
612 amBaseUrl: app.Config.GetString("controls.promAlertManager.baseUrl"),
613 amSchemes: []string{app.Config.GetString("controls.promAlertManager.schemes")},
614 alertInterval: alertInterval,
615 activeAlarms: make([]AlarmNotification, 0),
616 alarmHistory: make([]AlarmNotification, 0),
618 maxActiveAlarms: maxActiveAlarms,
619 maxAlarmHistory: maxAlarmHistory,
620 exceededActiveAlarmOn: false,
621 exceededAlarmHistoryOn: false,
622 alarmInfoPvFile: app.Config.GetString("controls.alarmInfoPvFile"),
628 NewAlarmManager("", 0, true).Run(true, 10)