前言
上一篇文章我们实现了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~
网硕互联帮助中心






评论前必须登录!
注册