The Service Oriented Monitoring (SOM) monitors web applications from the perspective of an user.
Szenarios are automatically correlated over users and regions.
Monitoring is done by instrumenting a chrome browser with the Chrome DevTools Protocol.
SOM uses a monitoring as code approach.
A szenario is the central concept of SOM.
A szenarion is a bit of go code that monitors an application.
All szenarios with the same name are correlated.
A szenario is run in the context of an user. The user provides a szenarion with password (and possibly more information).
Most users will have more than one szenario.
A user type (e.g. staf, client) assisiates users with a list of szenarios.
A region is the place (e.g. internal network, internet) where the monitoring originates from.
Regions are used to correlate the szenarios.
A szenarion is correlated first by user (by szenario runs of the user) then by region (by the users of the region).
A correlation group (i.e. user, region, szenario) has a level:
Level | Num Value |
---|---|
Unknown | 0 |
OK | 1 |
Issues | 2 |
Warning | 3 |
Down | 4 |
The level of a group is determined by summing the level of its children and dividing it by the number of children and then rounded.
The following restriction apply:
- If the last szenario run of a user was OK the level of the user is OK
- Issues is only reached if the level is at least Issues (2)
- Down is only reached if the level is at leat Down - 0.2 (3.8)
- If there are no children the level is Unknown
A correlation tree looks like this:
Szenario OWA: OK
Region development: OK
User som-user-dev-1: OK
08.08.2022 19:22:49: 2.16s OK
08.08.2022 19:17:22: 2.35s OK
08.08.2022 19:13:41: 4.43s OK
Region default: OK
User som-user-dev-1: OK
08.08.2022 13:16:04: 3.36s OK
08.08.2022 13:15:51: 3.19s OK
Szenario CourseSearch: OK
Region development: OK
User som-user-dev-1: OK
08.08.2022 19:23:51: 22.46s OK
08.08.2022 19:18:25: 22.37s OK
08.08.2022 19:14:45: "CourseSearch" step "Loading" failed: page load error net::ERR_INTERNET_DISCONNECTED
User som-world-dev-1: OK
09.08.2022 04:06:02: 22.22s OK
09.08.2022 04:03:37: 24.32s OK
09.08.2022 04:01:59: "CourseSearch" step "Loading" failed: page load error net::ERR_INTERNET_DISCONNECTED
User som-world-dev-2: Issues
09.08.2022 04:07:50: "CourseSearch" step "Loading" failed: page load error net::ERR_INTERNET_DISCONNECTED
09.08.2022 04:05:24: 22.26s OK
09.08.2022 04:03:00: 22.95s OK
Szenario PersSearch: OK
Region development: OK
User som-world-dev-1: Issues
09.08.2022 04:07:24: "PersSearch" step "Loading" failed: page load error net::ERR_INTERNET_DISCONNECTED
09.08.2022 04:05:01: 0.79s OK
09.08.2022 04:02:36: 0.84s OK
User som-world-dev-2: OK
09.08.2022 04:08:27: 0.94s OK
09.08.2022 04:06:46: 0.79s OK
09.08.2022 04:04:23: "PersSearch" step "Loading" failed: page load error net::ERR_INTERNET_DISCONNECTED
User som-user-dev-1: OK
08.08.2022 19:21:48: 0.75s OK
08.08.2022 19:16:22: 0.86s OK
08.08.2022 19:12:40: 0.97s OK
Region default: OK
User som-user-dev-1: OK
08.08.2022 13:15:36: 1.34s OK
08.08.2022 13:15:11: 1.81s OK
08.08.2022 12:48:14: 1.40s OK
Szenario Intranet: Down
Region development: Down
User som-user-dev-1: Down
08.08.2022 19:25:13: "Intranet" step "Loading" failed: timeout 1m0s
08.08.2022 19:19:47: "Intranet" step "Loading" failed: timeout 1m0s
08.08.2022 19:15:45: "Intranet" step "Loading" failed: page load error net::ERR_INTERNET_DISCONNECTED
To create a szenario you have to inherit *szenario.Base
:
type customSzenario struct {
*szenario.Base
Search string
}
and implement its Execute
method:
func (s customSzenario) Execute(engine szenario.Engine) (err error) {
engine.Step("Loading",
chromedp.Navigate("https://google.ch/"),
chromedp.WaitVisible(`#tophf`, chromedp.ByID),
)
engine.Step("Check",
engine.Body(engine.Contains("Google Search"), engine.Contains("About"), engine.Bigger(1000)),
)
return nil
}
engine.Step(stepName string, actions ...chromedp.Action)
executes a step with a name (stepName) and one or more actions.
All actions runnable by https://pkg.go.dev/github.com/chromedp/chromedp@v0.8.4#Run can be used.
chromedp.Navigate("https://google.ch/")
opens https://google.ch/
chromedp.WaitVisible(
#tophf, chromedp.ByID)
wait for an element with the ID #tophf to apear.
engine.Body(engine.Contains("Google Search"), engine.Contains("About"), engine.Bigger(1000)),
checks if the Body conatins "Google Search" and "About" and it's size is bigger than 1000 characters.
A input can be done like this:
engine.Step("searching",
chromedp.SendKeys(
`document.querySelector("body > div.L3eUgb > div.o3j99.ikrT4e.om7nvf > form > div:nth-child(1) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input")`,
fmt.Sprintf("%s\r", s.Search),
chromedp.ByJSPath, // copy JSPath from chrom developer tools
),
)
Finally the szenario has to be added to the szenarion config in loader.go
:
For a working google example see szenario/google.go
The following clicks the button with the ID buttonId
engine.Step("Click Accept", chromedp.Click("#buttonId", chromedp.ByID))
The following checks if a button with id buttonId is present and clicks it.
if ok := engine.IsPresent("#buttonId", chromedp.ByID); ok {
engine.Step("Click Accept", chromedp.Click("#buttonId", chromedp.ByID))
}
engine.Step("Login",
chromedp.WaitVisible(`#username`, chromedp.ByID),
chromedp.SendKeys(`#username`, s.User().Name()+"\r", chromedp.ByID),
chromedp.WaitReady(`#password`, chromedp.ByID),
chromedp.SendKeys(`#password`, s.User().Password()+"\r", chromedp.ByID),
)
The engine passed to the Execute method has the following methods:
Method Signature | Explaination |
---|---|
StepTimeout(name string, timeout time.Duration, actions ...chromedp.Action) error | StepTimeout executes a Step with an timeout |
Step(name string, actions ...chromedp.Action) | Step executes the actions given and records how long it takes |
IsPresent(sel interface{}, opts ...chromedp.QueryOption) bool | IsPresent checks if something is present |
SetStatus(key, val string) | SetStatus sets a status of the event |
AddErr(err error) | AddErr adds a error to the event |
Body(checks ...CheckFunc) chromedp.Action | Body is used to check the content of the page |
Contains(s string) CheckFunc | Contains looks for a string in the body |
NotContains(s string) CheckFunc | NotContains looks for a string in the body and errs if found |
Bigger(i int) CheckFunc | Bigger checks if the size of the body (in bytes) in bigger than i |
Strings(html *string) CheckFunc | Strings gets the body as plaintext |
Headless() bool | Headless indicates if the browser is headless (i.e. does not show on screen) |
WaitForEver() | WaitForEver blocks until the timeout is reached |
BreakWaitForUserInput() | BreakWaitForUserInput waits until any key is clicked on the cmdlint |
Log() *slog.Logger | Log returns the logger |
Dump() CheckFunc | Dump prints the body and its size to log (use with Body) |