云计算百科
云计算领域专业知识百科平台

从零实现一个分布式监控:Grafana的核心设计

前言

上一篇文章我们实现了Prometheus监控系统,但Prometheus只负责采集和存储数据,怎么把数据变成漂亮的图表和Dashboard呢?

Grafana是云原生监控的可视化层,支持多种数据源、丰富的图表类型和灵活的Dashboard配置。

今天我们从零实现一个Grafana的核心功能:

· 数据源管理(Prometheus模拟)
· Dashboard配置
· 图表渲染(Panel)
· 查询编辑器
· 模板变量
· 告警可视化

一、Grafana核心原理

1. 架构图

```
┌─────────────────────────────────────────────────────────────┐
│                        Grafana                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │  数据源     │  │  Dashboard  │  │  告警管理   │       │
│  │  管理       │  │  管理       │  │            │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │  查询引擎   │  │  图表渲染   │  │  模板变量   │       │
│  │            │  │  (Panel)    │  │            │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                      数据源集群                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │ Prometheus  │  │  InfluxDB   │  │  Elasticsearch│      │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                      Browser (UI)                          │
└─────────────────────────────────────────────────────────────┘
```

2. 核心概念

概念 说明
DataSource 数据源(Prometheus/MySQL/ES)
Dashboard 面板集合
Panel 单个图表
Query 查询表达式
Template Variable 模板变量

二、完整代码实现

1. 基础数据结构

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <math.h>
#include <ctype.h>

#define MAX_TITLE 256
#define MAX_QUERY 512
#define MAX_DATASOURCE 64
#define MAX_TARGETS 16
#define MAX_ROWS 20
#define MAX_COLS 12

// 数据源类型
typedef enum {
    DS_PROMETHEUS = 0,
    DS_INFLUXDB = 1,
    DS_ELASTICSEARCH = 2,
    DS_MYSQL = 3
} ds_type_t;

// 图表类型
typedef enum {
    PANEL_GRAPH = 0,
    PANEL_STAT,
    PANEL_TABLE,
    PANEL_HEATMAP,
    PANEL_BARGAUGE
} panel_type_t;

// 数据点
typedef struct data_point {
    time_t timestamp;
    double value;
    struct data_point *next;
} data_point_t;

// 时间序列
typedef struct time_series {
    char name[128];
    char labels[256];
    data_point_t *points;
    int point_count;
    struct time_series *next;
} time_series_t;

// 查询结果
typedef struct query_result {
    time_series_t *series;
    int series_count;
} query_result_t;

// 数据源
typedef struct datasource {
    char name[MAX_DATASOURCE];
    ds_type_t type;
    char url[256];
    char database[64];
    char username[64];
    char password[64];
    int default_ds;
    struct datasource *next;
} datasource_t;

// 查询目标
typedef struct query_target {
    char ref_id[4];
    char query[MAX_QUERY];
    char legend_format[64];
    int hide;
    struct query_target *next;
} query_target_t;

// Panel
typedef struct panel {
    char title[MAX_TITLE];
    panel_type_t type;
    int grid_x;
    int grid_y;
    int grid_w;
    int grid_h;
    char datasource[MAX_DATASOURCE];
    query_target_t *targets;
    int target_count;
    char field_config[256];
    struct panel *next;
} panel_t;

// Row(行)
typedef struct dashboard_row {
    char title[MAX_TITLE];
    int collapsed;
    panel_t *panels;
    int panel_count;
    struct dashboard_row *next;
} dashboard_row_t;

// Dashboard
typedef struct dashboard {
    char title[MAX_TITLE];
    char uid[32];
    dashboard_row_t *rows;
    int row_count;
    char tags[10][64];
    int tag_count;
    int time_from;
    int time_to;
    int refresh_interval;
} dashboard_t;

// 仪表盘管理器
typedef struct dashboard_manager {
    dashboard_t *dashboards;
    datasource_t *datasources;
    int dashboard_count;
    pthread_mutex_t mutex;
} dashboard_manager_t;
```

2. 数据源管理

```c
// 创建仪表盘管理器
dashboard_manager_t *dm_create(void) {
    dashboard_manager_t *dm = malloc(sizeof(dashboard_manager_t));
    dm->dashboards = NULL;
    dm->datasources = NULL;
    dm->dashboard_count = 0;
    pthread_mutex_init(&dm->mutex, NULL);
    printf("[Grafana] 仪表盘管理器创建\\n");
    return dm;
}

// 添加数据源
void dm_add_datasource(dashboard_manager_t *dm, const char *name,
                       ds_type_t type, const char *url) {
    pthread_mutex_lock(&dm->mutex);
    
    datasource_t *ds = malloc(sizeof(datasource_t));
    strcpy(ds->name, name);
    ds->type = type;
    strcpy(ds->url, url);
    ds->next = dm->datasources;
    dm->datasources = ds;
    
    pthread_mutex_unlock(&dm->mutex);
    printf("[Grafana] 添加数据源: %s\\n", name);
}

// 查找数据源
datasource_t *dm_get_datasource(dashboard_manager_t *dm, const char *name) {
    pthread_mutex_lock(&dm->mutex);
    
    datasource_t *ds = dm->datasources;
    while (ds) {
        if (strcmp(ds->name, name) == 0) {
            pthread_mutex_unlock(&dm->mutex);
            return ds;
        }
        ds = ds->next;
    }
    
    pthread_mutex_unlock(&dm->mutex);
    return NULL;
}
```

3. 查询引擎

```c
// 执行Prometheus查询(模拟)
query_result_t *execute_prometheus_query(const char *query, 
                                         time_t start, time_t end) {
    query_result_t *result = malloc(sizeof(query_result_t));
    result->series = NULL;
    result->series_count = 0;
    
    // 模拟数据
    time_series_t *ts = malloc(sizeof(time_series_t));
    strcpy(ts->name, query);
    strcpy(ts->labels, "{}");
    ts->points = NULL;
    ts->point_count = 0;
    ts->next = NULL;
    
    // 生成模拟数据点
    int count = 20 + rand() % 10;
    for (int i = 0; i < count; i++) {
        data_point_t *dp = malloc(sizeof(data_point_t));
        dp->timestamp = start + i * (end – start) / count;
        dp->value = 10 + rand() % 90 + (i % 10) * 0.5;
        dp->next = ts->points;
        ts->points = dp;
        ts->point_count++;
    }
    
    result->series = ts;
    result->series_count = 1;
    return result;
}

// 执行查询
query_result_t *dm_query(dashboard_manager_t *dm, const char *datasource,
                         const char *query, time_t start, time_t end) {
    datasource_t *ds = dm_get_datasource(dm, datasource);
    if (!ds) {
        fprintf(stderr, "数据源未找到: %s\\n", datasource);
        return NULL;
    }
    
    if (ds->type == DS_PROMETHEUS) {
        return execute_prometheus_query(query, start, end);
    }
    
    return NULL;
}

// 释放查询结果
void free_query_result(query_result_t *result) {
    if (!result) return;
    
    time_series_t *ts = result->series;
    while (ts) {
        time_series_t *next_ts = ts->next;
        data_point_t *dp = ts->points;
        while (dp) {
            data_point_t *next_dp = dp->next;
            free(dp);
            dp = next_dp;
        }
        free(ts);
        ts = next_ts;
    }
    free(result);
}
```

4. Dashboard配置

```c
// 创建Dashboard
dashboard_t *dm_create_dashboard(dashboard_manager_t *dm, const char *title) {
    pthread_mutex_lock(&dm->mutex);
    
    dashboard_t *db = malloc(sizeof(dashboard_t));
    strcpy(db->title, title);
    snprintf(db->uid, sizeof(db->uid), "dash-%d", time(NULL));
    db->rows = NULL;
    db->row_count = 0;
    db->tag_count = 0;
    db->time_from = 3600;  // 默认1小时
    db->time_to = 0;
    db->refresh_interval = 30;
    db->next = dm->dashboards;
    dm->dashboards = db;
    dm->dashboard_count++;
    
    pthread_mutex_unlock(&dm->mutex);
    printf("[Grafana] 创建Dashboard: %s\\n", title);
    return db;
}

// 添加Row
dashboard_row_t *dm_add_row(dashboard_t *db, const char *title) {
    dashboard_row_t *row = malloc(sizeof(dashboard_row_t));
    strcpy(row->title, title);
    row->collapsed = 0;
    row->panels = NULL;
    row->panel_count = 0;
    row->next = db->rows;
    db->rows = row;
    db->row_count++;
    return row;
}

// 添加Panel
panel_t *dm_add_panel(dashboard_row_t *row, const char *title, panel_type_t type,
                      const char *datasource, int w, int h) {
    panel_t *panel = malloc(sizeof(panel_t));
    strcpy(panel->title, title);
    panel->type = type;
    panel->grid_w = w;
    panel->grid_h = h;
    strcpy(panel->datasource, datasource);
    panel->targets = NULL;
    panel->target_count = 0;
    panel->next = row->panels;
    row->panels = panel;
    row->panel_count++;
    return panel;
}

// 添加查询目标
void dm_add_target(panel_t *panel, const char *ref_id, const char *query) {
    query_target_t *target = malloc(sizeof(query_target_t));
    strcpy(target->ref_id, ref_id);
    strcpy(target->query, query);
    target->legend_format[0] = '\\0';
    target->hide = 0;
    target->next = panel->targets;
    panel->targets = target;
    panel->target_count++;
}
```

5. 图表渲染(HTML输出)

```c
// 渲染数据点为图表
void render_graph_html(panel_t *panel, query_result_t *result, FILE *fp) {
    fprintf(fp, "<div class='panel graph'>\\n");
    fprintf(fp, "  <h3>%s</h3>\\n", panel->title);
    fprintf(fp, "  <div class='graph-container' style='height:250px;'>\\n");
    fprintf(fp, "    <svg width='100%%' height='100%%'>\\n");
    
    time_series_t *ts = result->series;
    int colors[] = {0x2196F3, 0x4CAF50, 0xFF9800, 0xF44336, 0x9C27B0};
    int color_idx = 0;
    
    while (ts) {
        // 计算范围
        double min_val = 1e9, max_val = -1e9;
        data_point_t *dp = ts->points;
        while (dp) {
            if (dp->value < min_val) min_val = dp->value;
            if (dp->value > max_val) max_val = dp->value;
            dp = dp->next;
        }
        double range = max_val – min_val;
        if (range < 1) range = 1;
        
        // 绘制折线
        fprintf(fp, "    <polyline points=\\"");
        dp = ts->points;
        int count = 0;
        while (dp) {
            double x = (count / (double)ts->point_count) * 100;
            double y = 100 – ((dp->value – min_val) / range) * 90 – 5;
            fprintf(fp, "%.1f,%.1f ", x, y);
            count++;
            dp = dp->next;
        }
        fprintf(fp, "\\" fill=\\"none\\" stroke=\\"#%06X\\" stroke-width=\\"2\\"/>\\n", 
                colors[color_idx % 5]);
        
        // 图例
        fprintf(fp, "    <text x='%d' y='%d' font-size='12' fill='#%06X'>%s</text>\\n",
                10, 20 + color_idx * 20, colors[color_idx % 5], ts->name);
        
        color_idx++;
        ts = ts->next;
    }
    
    fprintf(fp, "    </svg>\\n");
    fprintf(fp, "  </div>\\n");
    fprintf(fp, "</div>\\n");
}

// 渲染统计面板
void render_stat_html(panel_t *panel, query_result_t *result, FILE *fp) {
    fprintf(fp, "<div class='panel stat'>\\n");
    fprintf(fp, "  <h3>%s</h3>\\n", panel->title);
    
    if (result && result->series) {
        data_point_t *dp = result->series->points;
        double last_val = 0;
        while (dp) {
            last_val = dp->value;
            dp = dp->next;
        }
        fprintf(fp, "  <div class='stat-value'>%.2f</div>\\n", last_val);
    }
    fprintf(fp, "</div>\\n");
}

// 渲染Dashboard为HTML
void dm_render_dashboard_html(dashboard_manager_t *dm, const char *uid, FILE *fp) {
    // 查找Dashboard
    dashboard_t *db = dm->dashboards;
    while (db) {
        if (strcmp(db->uid, uid) == 0) break;
        db = db->next;
    }
    if (!db) {
        fprintf(fp, "<h1>Dashboard not found: %s</h1>\\n", uid);
        return;
    }
    
    fprintf(fp, "<!DOCTYPE html>\\n");
    fprintf(fp, "<html><head>\\n");
    fprintf(fp, "<title>%s – Grafana</title>\\n", db->title);
    fprintf(fp, "<style>\\n");
    fprintf(fp, "body{font-family:sans-serif;background:#1a1a2e;color:#eee;padding:20px;}\\n");
    fprintf(fp, ".dashboard{max-width:1400px;margin:0 auto;}\\n");
    fprintf(fp, ".row{background:#16213e;border-radius:8px;padding:15px;margin:10px 0;}\\n");
    fprintf(fp, ".panel{background:#1a1a3e;border-radius:8px;padding:15px;margin:10px 0;}\\n");
    fprintf(fp, ".panel.graph{border-left:4px solid #2196F3;}\\n");
    fprintf(fp, ".panel.stat{text-align:center;border-left:4px solid #4CAF50;}\\n");
    fprintf(fp, ".panel.stat .stat-value{font-size:48px;font-weight:bold;color:#4CAF50;}\\n");
    fprintf(fp, "h1{color:#eee;}\\n");
    fprintf(fp, "h3{margin:0 0 10px 0;color:#aaa;font-weight:normal;}\\n");
    fprintf(fp, "svg{background:#0d0d1a;border-radius:4px;}\\n");
    fprintf(fp, "</style>\\n");
    fprintf(fp, "</head><body>\\n");
    
    fprintf(fp, "<div class='dashboard'>\\n");
    fprintf(fp, "<h1>📊 %s</h1>\\n", db->title);
    
    // 渲染Rows
    dashboard_row_t *row = db->rows;
    while (row) {
        fprintf(fp, "<div class='row'>\\n");
        fprintf(fp, "<h2>%s</h2>\\n", row->title);
        
        panel_t *panel = row->panels;
        while (panel) {
            // 执行查询
            time_t now = time(NULL);
            time_t start = now – 3600;
            
            query_target_t *target = panel->targets;
            if (target) {
                query_result_t *result = dm_query(dm, panel->datasource,
                                                   target->query, start, now);
                if (result) {
                    if (panel->type == PANEL_GRAPH) {
                        render_graph_html(panel, result, fp);
                    } else if (panel->type == PANEL_STAT) {
                        render_stat_html(panel, result, fp);
                    } else {
                        render_graph_html(panel, result, fp);
                    }
                    free_query_result(result);
                }
            }
            
            panel = panel->next;
        }
        
        fprintf(fp, "</div>\\n");
        row = row->next;
    }
    
    fprintf(fp, "</div>\\n");
    fprintf(fp, "</body></html>\\n");
}
```

6. 测试代码

```c
void test_grafana() {
    printf("=== Grafana测试 ===\\n\\n");
    
    dashboard_manager_t *dm = dm_create();
    
    // 添加数据源
    dm_add_datasource(dm, "Prometheus", DS_PROMETHEUS, "http://localhost:9090");
    
    // 创建Dashboard
    dashboard_t *db = dm_create_dashboard(dm, "微服务监控");
    
    // 添加Row
    dashboard_row_t *row1 = dm_add_row(db, "服务指标");
    
    // 添加Panel
    panel_t *p1 = dm_add_panel(row1, "CPU使用率", PANEL_GRAPH, "Prometheus", 8, 4);
    dm_add_target(p1, "A", "cpu_usage{service=\\"user-service\\"}");
    
    panel_t *p2 = dm_add_panel(row1, "内存使用", PANEL_GRAPH, "Prometheus", 4, 4);
    dm_add_target(p2, "A", "memory_usage_mb{service=\\"user-service\\"}");
    
    panel_t *p3 = dm_add_panel(row1, "当前请求量", PANEL_STAT, "Prometheus", 3, 2);
    dm_add_target(p3, "A", "http_requests_total{service=\\"user-service\\"}");
    
    dashboard_row_t *row2 = dm_add_row(db, "业务指标");
    panel_t *p4 = dm_add_panel(row2, "订单成功率", PANEL_GRAPH, "Prometheus", 6, 3);
    dm_add_target(p4, "A", "order_success_rate");
    
    // 生成HTML
    char filename[256];
    snprintf(filename, sizeof(filename), "grafana_%s.html", db->uid);
    FILE *fp = fopen(filename, "w");
    if (fp) {
        dm_render_dashboard_html(dm, db->uid, fp);
        fclose(fp);
        printf("\\n仪表盘已生成: %s\\n", filename);
        printf("打开浏览器查看: file://%s\\n", filename);
    }
    
    printf("\\nDashboard信息:\\n");
    printf("  标题: %s\\n", db->title);
    printf("  UID: %s\\n", db->uid);
    printf("  Rows: %d\\n", db->row_count);
    printf("  Panels: %d\\n", 
           db->rows->panel_count + (db->rows->next ? db->rows->next->panel_count : 0));
    
    free(dm);
}

int main() {
    srand(time(NULL));
    test_grafana();
    return 0;
}
```

三、编译和运行

```bash
gcc -o grafana grafana.c -lpthread -lm
./grafana
```

四、Grafana vs 本实现

特性 本实现 Grafana
数据源 ✅ Prometheus ✅ 多种
Dashboard ✅ ✅
Panel ✅ 基础 ✅ 丰富
查询编辑器 ✅ 基础 ✅ 高级
模板变量 ❌ ✅
告警 ❌ ✅
权限管理 ❌ ✅

五、总结

通过这篇文章,你学会了:

· Grafana的核心架构
· 数据源管理
· Dashboard配置
· Panel渲染(Graph/Stat)
· 查询执行
· HTML仪表盘生成

Grafana是云原生监控的可视化标准。掌握它,你就理解了监控数据可视化的核心设计。

下一篇预告:《从零实现一个分布式告警:Alertmanager的核心设计》

评论区分享一下你用Grafana做过什么Dashboard~

赞(0)
未经允许不得转载:网硕互联帮助中心 » 从零实现一个分布式监控:Grafana的核心设计
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!