cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

PRO TIP - Automatic grid layout on a dashboard with monaco

Julius_Loman
DynaMight Legend
DynaMight Legend

It's quite common to have dashboards display many tiles for individual components - for example multiple services. Luckily Monaco supports Go Templating.  

Let's consider a Gen2 and a Gen3 dashboard where we want to easily generate a grid of tiles and just specify a set of parameters for each tile. It's just required to do some basic arithmetic to calculate the position of each tile and then wrap the tiles when we reach a defined width of the grid. Unfortunately, not even basic arithmetic is possible in the gotemplate engine used by monaco, so we need to do it creatively by using printf and len functions 😎


I believe the following sample is more than enough. Sample project is attached.

This is a YAML file for monaco specifying a list of tags for services with the tile title.

configs:
- id: sample-dashboard-with-auto-layout
  config:
    name: "Sample Dashboard with Auto Layout"
    parameters:
      services:
        type: value
        value:
        - tag: mytag1
          title: My Service with My Tag 1
        - tag: mytag2
          title: My Service with My Tag 2
        - tag: mytag3
          title: My Service with My Tag 3
        - tag: mytag4
          title: My Service with My Tag 4
        - tag: mytag5
          title: My Service with My Tag 5
        - tag: mytag6
          title: My Service with My Tag 6
        - tag: mytag7
          title: My Service with My Tag 7
        - tag: mytag8
          title: My Service with My Tag 8
    template: dashboard-autolayout.json
    skip: false
  type:
    api: dashboard

- id: gen3-sample-dashboard-with-auto-layout
  config:
    name: "Sample Dashboard with Auto Layout"
    parameters:
      services:
        type: value
        value:
        - tag: mytag1
          title: My Service with My Tag 1
        - tag: mytag2
          title: My Service with My Tag 2
        - tag: mytag3
          title: My Service with My Tag 3
        - tag: mytag4
          title: My Service with My Tag 4
        - tag: mytag5
          title: My Service with My Tag 5
        - tag: mytag6
          title: My Service with My Tag 6
        - tag: mytag7
          title: My Service with My Tag 7
        - tag: mytag8
          title: My Service with My Tag 8
    template: gen3-dashboard-autolayout.json
  type:
    document:
      kind: dashboard


The interesting part is the dashboard JSON (dashboard-autolayout.json) with gotemplate. We specify tile tileWidth, tileHeight for each tile and maxWidth = grid width.
Gen3 dashboard - gen3-dashboard-autolayout.json

{
    "version": 19,
    "variables": [],
    "tiles": {
{{- range $i, $e := .services}}    
{{- if $i}},{{- end}}
        "{{$i}}": {
            "type": "data",
            "title": "{{ $e.title }}",
            "query": "timeseries { `Response time` = avg(dt.service.request.response_time), value.A = avg(dt.service.request.response_time, scalar: true), Requests = sum(dt.service.request.count), value.B = avg(dt.service.request.count, scalar: true) }, union: TRUE, filter: { matchesValue(entityAttr(dt.entity.service, \"tags\"), \"{{ $e.tag }}\") }",
            "queryConfig": {
                "version": "15.4.0",
                "subQueries": [
                    {
                        "id": "A",
                        "isEnabled": true,
                        "datatype": "metrics",
                        "metric": {
                            "key": "dt.service.request.response_time",
                            "alias": "Response time",
                            "aggregation": "avg"
                        },
                        "filter": "dt.entity.service.tags = {{ $e.tag}} "
                    },
                    {
                        "id": "B",
                        "isEnabled": true,
                        "datatype": "metrics",
                        "metric": {
                            "key": "dt.service.request.count",
                            "alias": "Requests",
                            "aggregation": "sum"
                        },
                        "filter": "dt.entity.service.tags = {{ $e.tag}}  "
                    }
                ]
            },
            "subType": "dql-builder-metrics",
            "visualization": "lineChart",
            "davis": {
                "enabled": false,
                "davisVisualization": {
                    "isAvailable": true
                }
            },
            "visualizationSettings": {
                "autoSelectVisualization": false,
                "thresholds": [],
                "table": {
                    "columnTypeOverrides": [
                        {
                            "fields": [
                                "Response time",
                                "Requests"
                            ],
                            "value": "sparkline",
                            "id": 1755890244552
                        }
                    ]
                },
                "chartSettings": {
                    "legend": {
                        "hidden": false,
                        "position": "bottom"
                    },
                    "seriesOverrides": [
                        {
                            "seriesId": [
                                "Requests"
                            ],
                            "override": {
                                "geometry": "bar"
                            }
                        },
                        {
                            "seriesId": [
                                "Response time"
                            ],
                            "override": {
                                "color": {
                                    "Default": "var(--dt-colors-charts-categorical-color-01-default, #134fc9)"
                                }
                            }
                        }

                    ],
                    "hiddenLegendFields": [
                        "value.A",
                        "value.B",
                        "value.C"
                    ]
                }
            },
            "querySettings": {
                "maxResultRecords": 1000,
                "defaultScanLimitGbytes": 500,
                "maxResultMegaBytes": 1,
                "defaultSamplingRatio": 10,
                "enableSampling": false
            }
        }
{{- end}}        
    },
    "layouts": {

{{- $tileWidth := 6 }}
{{- $tileHeight := 4 }}
{{- $maxWidth := 18 }}
{{- $left := 0 }}{{ $top := 0 }}

{{- range $i, $e := .services}}    
        {{- if $i}},{{- end}}
        "{{$i}}": {
            "x": {{ $left }},
            "y": {{ $top }},
            "w": {{ $tileWidth}},
            "h": {{ $tileHeight }}
        }

        {{- $left = len (printf "%s%s" (printf "%*s" $left "") (printf "%*s" $tileWidth "")) }}
        {{- if gt $left $maxWidth}}
        {{- $left = 0 }}
        {{- $top = len (printf "%s%s" (printf "%*s" $top "") (printf "%*s" $tileHeight ""))}}
        {{- end}}        
{{- end}}        
 

   },
    "importedWithCode": false,
    "settings": {}
}


Gen2 dashboard - dashboard-autolayout.json:

{
  "dashboardMetadata": {
    "dashboardFilter": null,
    "hasConsistentColors": false,
    "name": "{{.name}}",
    "owner": "Monaco",
    "preset": true,
    "shared": true,
    "tilesNameSize": null
  },
  "tiles": [
{{- $tileWidth := 418 }}
{{- $tileHeight := 266 }}
{{- $maxWidth := 1300 }}
{{- $left := 0 }}{{ $top := 0 }}

{{- range $i, $e := .services}}    
{{- if $i}},{{- end}}
    {
      "name": "{{ $e.title }}",
      "tileType": "DATA_EXPLORER",
      "configured": true,
      "bounds": {
        "top": {{ $top }},
        "left": {{ $left }},
        "width": {{ $tileWidth}},
        "height": {{ $tileHeight }}
      },
      "tileFilter": {},
      "isAutoRefreshDisabled": false,
      "customName": "{{ $e.title}}",
      "queries": [
        {
          "id": "A",
          "metric": "builtin:service.response.server",
          "spaceAggregation": "AUTO",
          "timeAggregation": "DEFAULT",
          "splitBy": [],
          "sortBy": "DESC",
          "sortByDimension": "",
          "filterBy": {
            "filterOperator": "AND",
            "nestedFilters": [
              {
                "filter": "dt.entity.service",
                "filterType": "TAG",
                "filterOperator": "OR",
                "nestedFilters": [],
                "criteria": [
                  {
                    "value": "{{ $e.tag }}",
                    "evaluator": "IN"
                  }
                ]
              }
            ],
            "criteria": []
          },
          "limit": 20,
          "rate": "NONE",
          "enabled": true
        },
        {
          "id": "B",
          "metric": "builtin:service.requestCount.total",
          "spaceAggregation": "AUTO",
          "timeAggregation": "DEFAULT",
          "splitBy": [],
          "sortBy": "DESC",
          "sortByDimension": "",
          "filterBy": {
            "filterOperator": "AND",
            "nestedFilters": [
              {
                "filter": "dt.entity.service",
                "filterType": "TAG",
                "filterOperator": "OR",
                "nestedFilters": [],
                "criteria": [
                  {
                    "value": "{{ $e.tag }}",
                    "evaluator": "IN"
                  }
                ]
              }
            ],
            "criteria": []
          },
          "limit": 20,
          "rate": "NONE",
          "enabled": true
        },
        {
          "id": "C",
          "metric": "builtin:service.response.client",
          "spaceAggregation": "AUTO",
          "timeAggregation": "DEFAULT",
          "splitBy": [],
          "sortBy": "DESC",
          "sortByDimension": "",
          "filterBy": {
            "filterOperator": "AND",
            "nestedFilters": [
              {
                "filter": "dt.entity.service",
                "filterType": "TAG",
                "filterOperator": "OR",
                "nestedFilters": [],
                "criteria": [
                  {
                    "value": "{{ $e.tag }}",
                    "evaluator": "IN"
                  }
                ]
              }
            ],
            "criteria": []
          },
          "limit": 20,
          "rate": "NONE",
          "enabled": true
        }
      ],
      "visualConfig": {
        "type": "GRAPH_CHART",
        "global": {},
        "rules": [
          {
            "matcher": "A:",
            "unitTransform": "auto",
            "valueFormat": "auto",
            "properties": {
              "color": "RED",
              "seriesType": "LINE"
            },
            "seriesOverrides": []
          },
          {
            "matcher": "B:",
            "unitTransform": "auto",
            "valueFormat": "auto",
            "properties": {
              "color": "ROYALBLUE",
              "seriesType": "COLUMN"
            },
            "seriesOverrides": []
          },
          {
            "matcher": "C:",
            "properties": {
              "color": "DEFAULT"
            },
            "seriesOverrides": []
          }
        ],
        "axes": {
          "xAxis": {
            "displayName": "",
            "visible": true
          },
          "yAxes": [
            {
              "displayName": "",
              "visible": true,
              "min": "AUTO",
              "max": "AUTO",
              "position": "LEFT",
              "queryIds": [
                "A",
                "C"
              ],
              "defaultAxis": true
            },
            {
              "displayName": "",
              "visible": true,
              "min": "AUTO",
              "max": "AUTO",
              "position": "RIGHT",
              "queryIds": [
                "B"
              ],
              "defaultAxis": true
            }
          ]
        },
        "heatmapSettings": {
          "yAxis": "VALUE",
          "showLabels": false
        },
        "singleValueSettings": {
          "showTrend": true,
          "showSparkLine": true,
          "linkTileColorToThreshold": true
        },
        "thresholds": [
          {
            "axisTarget": "LEFT",
            "rules": [
              {
                "color": "#7dc540"
              },
              {
                "color": "#f5d30f"
              },
              {
                "color": "#dc172a"
              }
            ],
            "visible": true
          }
        ],
        "tableSettings": {
          "hiddenColumns": []
        },
        "graphChartSettings": {
          "connectNulls": false
        },
        "honeycombSettings": {
          "showHive": true,
          "showLegend": true,
          "showLabels": false
        }
      },
      "queriesSettings": {
        "resolution": ""
      }
    }
        {{- $left = len (printf "%s%s" (printf "%*s" $left "") (printf "%*s" $tileWidth "")) }}
        {{- if gt $left $maxWidth}}
        {{- $left = 0 }}
        {{- $top = len (printf "%s%s" (printf "%*s" $top "") (printf "%*s" $tileHeight ""))}}
        {{- end}}
{{- end}}
]
}


When deployed, we will get a nice grid of tiles on a dashboard, and we can add new services just in the YAML without touching the JSON:

Julius_Loman_0-1755891747634.png

 

Julius_Loman_0-1755762910717.png

 



Certified Dynatrace Master | Alanata a.s., Slovakia, Dynatrace Master Partner
6 REPLIES 6

lubrman
Advisor

Elegant solution

lubrman
Advisor

For completeness, it’s even better to extract the metrics into YAML and then send them into JSON. This way, each tile can display different data. Such an approach would also allow using the tiles within a CI/CD pipeline, where, for example, during an application deployment a dashboard could be created based on the specific requirements of the application owner.

Sure, this example above is the bare minimum for showcasing the possibilities. It's also possible to use different tile types of course, like service or host health.

What I was unable to do was provide a reference to another Monaco configuration in the list. @nico_riedmann  any suggestions?  For example, to have the tag a reference to an auto-tag deployed by Monaco in the same project. Monaco does not seem to resolve dependencies in this parameter type.

Certified Dynatrace Master | Alanata a.s., Slovakia, Dynatrace Master Partner

36Krazyfists
Helper

This is really cool.  I'd like to see something similar for modern dashboards too.  Now that RUM on Grail is available, we're starting to move more and more of our dashboards to the new framework, but I find the JSON not as friendly to read as gen 2 dashboards.

@36Krazyfists certainly possible with Gen3 dashboards. I agree, the new dashboard JSONs are less friendly for editing 🙃

Certified Dynatrace Master | Alanata a.s., Slovakia, Dynatrace Master Partner

@36Krazyfists I updated this post with Gen3 dashboard example - same approach, just you have to loop twice through the list.

Certified Dynatrace Master | Alanata a.s., Slovakia, Dynatrace Master Partner

Featured Posts