github.com/matrixorigin/matrixone@v1.2.0/pkg/util/metric/v2/dashboard/grafana_dashboard.go (about) 1 // Copyright 2023 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dashboard 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "net/http" 22 23 "github.com/K-Phoen/grabana" 24 "github.com/K-Phoen/grabana/axis" 25 "github.com/K-Phoen/grabana/dashboard" 26 "github.com/K-Phoen/grabana/graph" 27 "github.com/K-Phoen/grabana/row" 28 "github.com/K-Phoen/grabana/target/prometheus" 29 "github.com/K-Phoen/grabana/timeseries" 30 tsaxis "github.com/K-Phoen/grabana/timeseries/axis" 31 "github.com/K-Phoen/grabana/variable/datasource" 32 "github.com/K-Phoen/grabana/variable/interval" 33 "github.com/K-Phoen/grabana/variable/query" 34 ) 35 36 var ( 37 moFolderName = "Matrixone" 38 ) 39 40 type DashboardCreator struct { 41 cli *grabana.Client 42 dataSource string 43 extraFilterFunc func() string 44 by string 45 filterOptions []dashboard.Option 46 } 47 48 func NewCloudDashboardCreator( 49 host, 50 username, 51 password, 52 dataSource string) *DashboardCreator { 53 dc := &DashboardCreator{ 54 cli: grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)), 55 dataSource: dataSource, 56 } 57 dc.extraFilterFunc = dc.getCloudFilters 58 dc.by = "pod" 59 dc.initCloudFilterOptions() 60 return dc 61 } 62 63 func NewLocalDashboardCreator( 64 host, 65 username, 66 password, 67 dataSource string) *DashboardCreator { 68 dc := &DashboardCreator{ 69 cli: grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)), 70 dataSource: dataSource, 71 } 72 dc.extraFilterFunc = dc.getLocalFilters 73 dc.by = "instance" 74 dc.initLocalFilterOptions() 75 return dc 76 } 77 78 func NewK8SDashboardCreator( 79 host, 80 username, 81 password, 82 dataSource string) *DashboardCreator { 83 dc := &DashboardCreator{ 84 cli: grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)), 85 dataSource: dataSource, 86 } 87 dc.extraFilterFunc = dc.getK8SFilters 88 dc.by = "pod" 89 dc.initK8SFilterOptions() 90 return dc 91 } 92 93 func NewCloudCtrlPlaneDashboardCreator( 94 host, 95 username, 96 password, 97 dataSource string) *DashboardCreator { 98 dc := &DashboardCreator{ 99 cli: grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)), 100 dataSource: AutoUnitPrometheusDatasource, 101 } 102 dc.extraFilterFunc = dc.getCloudFilters 103 dc.by = "pod" 104 dc.initCloudCtrlPlaneFilterOptions(dataSource) 105 return dc 106 } 107 108 func (c *DashboardCreator) Create() error { 109 if err := c.initTxnDashboard(); err != nil { 110 return err 111 } 112 113 if err := c.initLogTailDashboard(); err != nil { 114 return err 115 } 116 117 if err := c.initTaskDashboard(); err != nil { 118 return err 119 } 120 121 if err := c.initFileServiceDashboard(); err != nil { 122 return err 123 } 124 125 if err := c.initRPCDashboard(); err != nil { 126 return err 127 } 128 129 if err := c.initMemDashboard(); err != nil { 130 return err 131 } 132 133 if err := c.initRuntimeDashboard(); err != nil { 134 return err 135 } 136 137 if err := c.initTraceDashboard(); err != nil { 138 return err 139 } 140 141 if err := c.initProxyDashboard(); err != nil { 142 return err 143 } 144 145 if err := c.initFrontendDashboard(); err != nil { 146 return err 147 } 148 149 return nil 150 } 151 152 func (c *DashboardCreator) createFolder(name string) (*grabana.Folder, error) { 153 return c.cli.FindOrCreateFolder(context.Background(), name) 154 } 155 156 func (c *DashboardCreator) withGraph( 157 title string, 158 span float32, 159 pql string, 160 legend string, 161 opts ...axis.Option) row.Option { 162 return c.withMultiGraph( 163 title, 164 span, 165 []string{pql}, 166 []string{legend}, 167 opts..., 168 ) 169 } 170 171 func (c *DashboardCreator) withMultiGraph( 172 title string, 173 span float32, 174 queries []string, 175 legends []string, 176 axisOpts ...axis.Option) row.Option { 177 opts := []graph.Option{ 178 graph.Span(span), 179 graph.DataSource(c.dataSource), 180 graph.LeftYAxis(axisOpts...)} 181 182 for i, query := range queries { 183 opts = append(opts, 184 graph.WithPrometheusTarget( 185 query, 186 prometheus.Legend(legends[i]), 187 )) 188 } 189 190 return row.WithGraph( 191 title, 192 opts..., 193 ) 194 } 195 196 func (c *DashboardCreator) getHistogram( 197 title string, 198 metric string, 199 percents []float64, 200 column float32, 201 axisOptions ...axis.Option) row.Option { 202 return c.getHistogramWithExtraBy(title, metric, percents, column, "", axisOptions...) 203 } 204 205 func SpanNulls(always bool) timeseries.Option { 206 return func(ts *timeseries.TimeSeries) error { 207 ts.Builder.TimeseriesPanel.FieldConfig.Defaults.Custom.SpanNulls = always 208 return nil 209 } 210 } 211 212 func (c *DashboardCreator) getPercentHist( 213 title string, 214 metric string, 215 percents []float64, 216 opts ...timeseries.Option) row.Option { 217 options := []timeseries.Option{ 218 timeseries.DataSource(c.dataSource), 219 timeseries.FillOpacity(0), 220 timeseries.Height("300px"), 221 timeseries.Axis(tsaxis.Unit("s")), 222 } 223 options = append(options, opts...) 224 for i := 0; i < len(percents); i++ { 225 percent := percents[i] 226 query := fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval])) by (le, "+c.by+"))", percent, metric) 227 legend := fmt.Sprintf("P%.0f", percent*100) 228 options = append(options, timeseries.WithPrometheusTarget( 229 query, 230 prometheus.Legend(legend), 231 )) 232 } 233 return row.WithTimeSeries(title, options...) 234 } 235 236 func (c *DashboardCreator) getTimeSeries( 237 title string, pql []string, legend []string, 238 opts ...timeseries.Option) row.Option { 239 options := []timeseries.Option{ 240 timeseries.DataSource(c.dataSource), 241 timeseries.FillOpacity(0), 242 timeseries.Height("300px"), 243 } 244 options = append(options, opts...) 245 for i := range pql { 246 options = append(options, timeseries.WithPrometheusTarget( 247 pql[i], 248 prometheus.Legend(legend[i]), 249 )) 250 } 251 return row.WithTimeSeries(title, options...) 252 } 253 254 func (c *DashboardCreator) getHistogramWithExtraBy( 255 title string, 256 metric string, 257 percents []float64, 258 column float32, 259 extraBy string, 260 axisOptions ...axis.Option) row.Option { 261 262 var queries []string 263 var legends []string 264 for i := 0; i < len(percents); i++ { 265 percent := percents[i] 266 267 query := fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval])) by (le))", percent, metric) 268 legend := fmt.Sprintf("P%.2f%%", percent*100) 269 if len(extraBy) > 0 { 270 query = fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval])) by (le, %s))", percent, metric, extraBy) 271 legend = fmt.Sprintf("{{ "+extraBy+" }}(P%.2f%%)", percent*100) 272 } 273 queries = append(queries, query) 274 legends = append(legends, legend) 275 } 276 return c.withMultiGraph( 277 title, 278 column, 279 queries, 280 legends, 281 axisOptions..., 282 ) 283 } 284 285 func (c *DashboardCreator) getMultiHistogram( 286 metrics []string, 287 legends []string, 288 percents []float64, 289 columns []float32, 290 axisOptions ...axis.Option) []row.Option { 291 var options []row.Option 292 for i := 0; i < len(percents); i++ { 293 percent := percents[i] 294 295 var queries []string 296 for _, metric := range metrics { 297 queries = append(queries, 298 fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval])) by (le))", percent, metric)) 299 } 300 301 options = append(options, 302 c.withMultiGraph( 303 fmt.Sprintf("P%f time", percent*100), 304 columns[i], 305 queries, 306 legends, 307 axisOptions...)) 308 } 309 return options 310 } 311 312 func (c *DashboardCreator) withRowOptions(rows ...dashboard.Option) []dashboard.Option { 313 rows = append(rows, 314 dashboard.AutoRefresh("30s"), 315 dashboard.Time("now-30m", "now"), 316 dashboard.VariableAsInterval( 317 "interval", 318 interval.Default("1m"), 319 interval.Values([]string{"1m", "5m", "10m", "30m", "1h", "6h", "12h"}), 320 )) 321 return append(rows, c.filterOptions...) 322 } 323 324 func (c *DashboardCreator) getMetricWithFilter(name string, filter string) string { 325 var metric bytes.Buffer 326 extraFilters := c.extraFilterFunc() 327 328 if len(filter) == 0 && len(extraFilters) == 0 { 329 return name 330 } 331 332 metric.WriteString(name) 333 metric.WriteString("{") 334 if filter != "" { 335 metric.WriteString(filter) 336 if len(extraFilters) > 0 { 337 metric.WriteString(",") 338 } 339 } 340 if len(extraFilters) > 0 { 341 metric.WriteString(c.extraFilterFunc()) 342 } 343 metric.WriteString("}") 344 return metric.String() 345 } 346 347 func (c *DashboardCreator) getCloudFilters() string { 348 return `matrixone_cloud_main_cluster=~"$physicalCluster", matrixorigin_io_owner=~"$owner", pod=~"$pod"` 349 } 350 351 func (c *DashboardCreator) initCloudFilterOptions() { 352 c.filterOptions = append(c.filterOptions, 353 dashboard.VariableAsQuery( 354 "physicalCluster", 355 query.DataSource(c.dataSource), 356 query.DefaultAll(), 357 query.IncludeAll(), 358 query.Multiple(), 359 query.Label("main_cluster"), 360 query.Request(`label_values(up, matrixone_cloud_main_cluster)`), 361 ), 362 dashboard.VariableAsQuery( 363 "owner", 364 query.DataSource(c.dataSource), 365 query.DefaultAll(), 366 query.IncludeAll(), 367 query.Multiple(), 368 query.Label("owner"), 369 query.Request(`label_values(up{matrixone_cloud_main_cluster=~"$physicalCluster"}, matrixorigin_io_owner)`), 370 query.AllValue(".*"), 371 query.Refresh(query.TimeChange), 372 ), 373 dashboard.VariableAsQuery( 374 "pod", 375 query.DataSource(c.dataSource), 376 query.DefaultAll(), 377 query.IncludeAll(), 378 query.Multiple(), 379 query.Label("pod"), 380 query.Request(`label_values(up{matrixone_cloud_main_cluster="$physicalCluster", matrixorigin_io_owner=~"$owner"},pod)`), 381 query.Refresh(query.TimeChange), 382 )) 383 } 384 385 const Prometheus = "prometheus" 386 const AutoUnitPrometheusDatasource = `${ds_prom}` 387 388 func (c *DashboardCreator) initCloudCtrlPlaneFilterOptions(metaDatasource string) { 389 c.filterOptions = append(c.filterOptions, 390 dashboard.VariableAsQuery( 391 "unit", 392 query.DataSource(metaDatasource), 393 query.Label("unit"), 394 query.Request(`label_values(mo_cluster_info, unit)`), 395 ), 396 dashboard.VariableAsDatasource( 397 "ds_prom", 398 datasource.Type(Prometheus), 399 datasource.Regex(`/$unit-prometheus/`), 400 ), 401 dashboard.VariableAsQuery( 402 "physicalCluster", 403 query.DataSource(`${ds_prom}`), 404 query.Label("main_cluster"), 405 query.Request(`label_values(up, matrixone_cloud_main_cluster)`), 406 ), 407 dashboard.VariableAsQuery( 408 "owner", 409 query.DataSource(`${ds_prom}`), 410 query.DefaultAll(), 411 query.IncludeAll(), 412 query.Multiple(), 413 query.Label("owner"), 414 query.Request(`label_values(up{matrixone_cloud_main_cluster=~"$physicalCluster"}, matrixorigin_io_owner)`), 415 query.AllValue(".*"), 416 query.Refresh(query.TimeChange), 417 ), 418 dashboard.VariableAsQuery( 419 "pod", 420 query.DataSource(`${ds_prom}`), 421 query.DefaultAll(), 422 query.IncludeAll(), 423 query.Multiple(), 424 query.Label("pod"), 425 query.Request(`label_values(up{matrixone_cloud_main_cluster="$physicalCluster", matrixorigin_io_owner=~"$owner"},pod)`), 426 query.Refresh(query.TimeChange), 427 )) 428 } 429 430 func (c *DashboardCreator) getLocalFilters() string { 431 return "" 432 } 433 434 func (c *DashboardCreator) initLocalFilterOptions() { 435 c.filterOptions = append(c.filterOptions, 436 dashboard.VariableAsQuery( 437 "instance", 438 query.DataSource(c.dataSource), 439 query.DefaultAll(), 440 query.IncludeAll(), 441 query.Multiple(), 442 query.Label("instance"), 443 query.Request("label_values(instance)"), 444 )) 445 } 446 447 func (c *DashboardCreator) getK8SFilters() string { 448 return `namespace=~"$namespace", pod=~"$pod"` 449 } 450 451 func (c *DashboardCreator) initK8SFilterOptions() { 452 c.filterOptions = append(c.filterOptions, 453 dashboard.VariableAsQuery( 454 "namespace", 455 query.DataSource(c.dataSource), 456 query.DefaultAll(), 457 query.IncludeAll(), 458 query.Multiple(), 459 query.Label("namespace"), 460 query.Request("label_values(namespace)"), 461 ), 462 dashboard.VariableAsQuery( 463 "pod", 464 query.DataSource(c.dataSource), 465 query.DefaultAll(), 466 query.IncludeAll(), 467 query.Multiple(), 468 query.Label("pod"), 469 query.Request("label_values(pod)"), 470 )) 471 }