redis源码sparkline详解
- sparkline功能介绍
- sparkline实现方法-代码解析
- sparkline.h文件解析
- sparkline.c文件解析
- OVER
sparkline功能介绍
当我第一次打开sparkline源文件的时候我的表情是这样的
就像我正津津有味地看着汪汪队呢,突然换了台,换到了相亲节目。这是在干啥呢??
进入主题,先说功能:redis中sparkline功能是给定一批值 + 值对应的标签,以字符作为像素点 在窗口上将这种值和标签的对应关系表现出来,用字符生成折线图。
正常情况下的折线图如下
sparkline下的折线图长下面这个样子(无填充,无标签)。
或者有填充的话是下面这个样子(有填充,无标签)。
再或者有标签的情况下是这个样子。
sparkline实现方法-代码解析
sparkline.h文件解析
/* sparkline.h -- ASCII Sparklines header file
*
* ---------------------------------------------------------------------------
*
* Copyright(C) 2011-2014 Salvatore Sanfilippo <antirez@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __SPARKLINE_H
#define __SPARKLINE_H
/* A sequence is represented of many "samples" */
struct sample { //-->对应正常折线图中折线上的黑色圆点
double value; //样本点的值 -->对应正常折线图中折线上黑色圆点Y坐标的值
char *label; //样本点的标签 -->对应正常折线图中折线黑色圆点对应x轴处的月份值,此处是1,2..等
};
struct sequence { //保存整个折线图中折线上所有黑色圆点的结构
int length; //记录插入的黑色圆点有多少个。
int labels; //记录插入的label有值的黑色圆点有多少个。
struct sample *samples;//用来记录n个 黑色圆点 的具体y轴值和label信息
double min, max;//记录插入的黑色圆点中value的最大值和最小值。此处的最大值和最小值是samples所有点中value的最大值和最小值。
};
#define SPARKLINE_NO_FLAGS 0 //未用
#define SPARKLINE_FILL 1 //是否填充
#define SPARKLINE_LOG_SCALE 2 //是否将黑色圆点的y轴值进行log(value)对数化,对数据取对数可以极大缩小值的范围,是相邻的变化幅度平缓化,但又保留了原本的大小变化关系。
struct sequence *createSparklineSequence(void);//
void sparklineSequenceAddSample(struct sequence *seq, double value, char *label);
void freeSparklineSequence(struct sequence *seq);
sds sparklineRenderRange(sds output, struct sequence *seq, int rows, int offset, int len, int flags);
sds sparklineRender(sds output, struct sequence *seq, int columns, int rows, int flags);
#endif /* __SPARKLINE_H */
sparkline.c文件解析
/* sparkline.c -- ASCII Sparklines
* This code is modified from http://github.com/antirez/aspark and adapted
* in order to return SDS strings instead of outputting directly to
* the terminal.
*
* ---------------------------------------------------------------------------
*
* Copyright(C) 2011-2014 Salvatore Sanfilippo <antirez@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "server.h"
#include <math.h>
/* This is the charset used to display the graphs, but multiple rows are used
* to increase the resolution. */
static char charset[] = "_-`";//用来表示不填充折线图中黑色圆点的ASCII字符
static char charset_fill[] = "_o#";//用来表示需填充的折线图中折线上黑色原点的ASCII字符
static int charset_len = sizeof(charset)-1;
static int label_margin_top = 1;//label和折线图图区域的留白行数。
/* ----------------------------------------------------------------------------
* Sequences are arrays of samples we use to represent data to turn
* into sparklines. This is the API in order to generate a sparkline:
*
* struct sequence *seq = createSparklineSequence();
* sparklineSequenceAddSample(seq, 10, NULL);
* sparklineSequenceAddSample(seq, 20, NULL);
* sparklineSequenceAddSample(seq, 30, "last sample label");
* sds output = sparklineRender(sdsempty(), seq, 80, 4, SPARKLINE_FILL);
* freeSparklineSequence(seq);
* ------------------------------------------------------------------------- */
/* Create a new sequence. */
//创建一个空的sequence用来存放黑点
struct sequence *createSparklineSequence(void) {
struct sequence *seq = zmalloc(sizeof(*seq));
seq->length = 0;
seq->samples = NULL;
return seq;
}
/* Add a new sample into a sequence. */
//将一个新的黑点加入到保存折线上黑点的结构中。并更新最大值最小值等
void sparklineSequenceAddSample(struct sequence *seq, double value, char *label) {
label = (label == NULL || label[0] == '\0') ? NULL : zstrdup(label);
//sequence中记录最大最小值
if (seq->length == 0) {
seq->min = seq->max = value;
} else {
//更新最大最小值
if (value < seq->min) seq->min = value;
else if (value > seq->max) seq->max = value;
}
//扩增长度,存储sample
seq->samples = zrealloc(seq->samples,sizeof(struct sample)*(seq->length+1));
seq->samples[seq->length].value = value;
//存入对应的表达内容
seq->samples[seq->length].label = label;
//序列包含黑点数++
seq->length++;
//序列包含 含有label的黑点数 ++
if (label) seq->labels++;
}
/* Free a sequence. */
//释放存储序列
void freeSparklineSequence(struct sequence *seq) {
int j;
for (j = 0; j < seq->length; j++)
zfree(seq->samples[j].label);
zfree(seq->samples);
zfree(seq);
}
/* ----------------------------------------------------------------------------
* ASCII rendering of sequence
* 渲染序列,将sequence中保存的黑点渲染成以 字符 表现的可视的图
* output: 渲染输出的字符串追加到output中
* seq: 需要被渲染的包含n个黑点的序列
* rows: 渲染时将图像(字符位置区间)压缩到rows行中
* offset: 从seq中第几个黑点才是渲染
* len: 连续渲染几个黑点
* flags:是否填充,是否对value取对数的开关
* ------------------------------------------------------------------------- */
/* Render part of a sequence, so that render_sequence() call call this function
* with differnent parts in order to create the full output without overflowing
* the current terminal columns. */
sds sparklineRenderRange(sds output, struct sequence *seq, int rows, int offset, int len, int flags) {
int j;
//序列包含黑点的最大值和最小值的差
double relmax = seq->max - seq->min;
//charset= _-` 单行渲染可用的 3个字符像素,所以要求rows行的话需要3*rows个字符渲染。可以认为这个作画窗口被设定为Y轴有rows行*3的字符像素点。
int steps = charset_len*rows;
int row = 0;
char *chars = zmalloc(len);
int loop = 1;
//是否填充
int opt_fill = flags & SPARKLINE_FILL;
//是否将value进行对数化处理
int opt_log = flags & SPARKLINE_LOG_SCALE;
if (opt_log) {
//获取对数之后的黑色点分布的区间大小
relmax = log(relmax+1);//此处+1是为了防止relmax是个小于1的数,到时算出来的relmax是个负值
} else if (relmax == 0) {
relmax = 1;
}
//逐行由高到低操作扫描
while(loop) {
loop = 0;
//每一行扫描完后,chars中就包含了这一行中所有位置(列)的像素字符
//初始化为空白填充
memset(chars,' ',len);
//在当前行上,对每个samples逐个确认,在此行上用什么 字符像素填充?
//case 1 :当前行大于 sample黑点所在的行(此时还在黑点以上的行进行扫描),则不使用字符像素填充。
//case 2 : 当前行等于 sample黑点所在的行,则使用字符像素进行填充。
//case 3 : 当前行小于 sample黑点所在的行,则按是否要求 来进行空白区域的填充。
for (j = 0; j < len; j++) {//只查看要求的列
struct sample *s = &seq->samples[j+offset];
//获取当前sample相对min的值
double relval = s->value - seq->min;
int step;
//如果需要对数化,进行对数化
if (opt_log) relval = log(relval+1);
//获取此sample的Y轴值具体落到了Y轴上 的 哪个 字符像素 上。Y轴上字符像素区间为0 -- charset_len*rows
step = (int) (relval*steps)/relmax;
//如果step小于0,则是第一排
if (step < 0) step = 0;
//如果大于最大steps.则step取steps-1(落在最顶层)
if (step >= steps) step = steps-1;
//如果从上到下此时遍历的行数小于 整个画板Y轴的行数
if (row < rows) {
/* Print the character needed to create the sparkline */
//charidx < 0时,说明此时正在扫描和黑点同列上面哪个行的位置
//0 <= charidx <charset_len时,说明此时正在扫描黑点所处的位置
//charset_len < charidx时,说明此时正在扫描和黑点同列下面哪个行的位置
int charidx = step-((rows-row-1)*charset_len);
loop = 1;
//黑点位置,使用对应字符填充
if (charidx >= 0 && charidx < charset_len) {
chars[j] = opt_fill ? charset_fill[charidx] :
charset[charidx];
//折线上黑点同列下面的位置,判断使用'|'进行填充
} else if(opt_fill && charidx >= charset_len) {
chars[j] = '|';
}
} else {//此时已经扫描了rows行,现在在写label的行
/* Labels spacing */
//如果seq中包含有标签的点 && 此时扫描的位置所在的行为label留白区域,那么循环。
if (seq->labels && row-rows < label_margin_top) {
loop = 1;
break;
}
/* Print the label if needed. */
//按当前行取label中对应位置字符添加到chars中
//此处最终展示如上面图中带标签折线图中label "peak"展示的一样,
// p
// e
// a
// k
if (s->label) {
int label_len = strlen(s->label);
int label_char = row - rows - label_margin_top;
if (label_len > label_char) {
//label还有字符需要继续写
loop = 1;
chars[j] = s->label[label_char];
}
}
}
}
//1 还在折线图区域范围内 或者 2还有一些label中的字符 还没输出完
if (loop) {
//下一行
row++;
//将扫描到的此行内容追加到output中。
output = sdscatlen(output,chars,len);
//追加换行符
output = sdscatlen(output,"\n",1);
}
}
zfree(chars);
return output;
}
//按columns大小 对sequece生成折线图。此处这么弄主要是防止点过多,万一在已有的显示边界中无法包含。通过columns将点按照columns个数分段 一层一层的渲染到显示上。
/* Turn a sequence into its ASCII representation */
sds sparklineRender(sds output, struct sequence *seq, int columns, int rows, int flags) {
int j;
//按columns大小分段渲染黑点
for (j = 0; j < seq->length; j += columns) {
//获取此次需要渲染的sublen长度
int sublen = (seq->length-j) < columns ? (seq->length-j) : columns;
//一次渲染后加"\n"
if (j != 0) output = sdscatlen(output,"\n",1);
output = sparklineRenderRange(output, seq, rows, j, sublen, flags);
}
return output;
}
OVER