Redis源码分析(五)--- sparkline微线图
sparkline这个单词,我第一次看的时候,也不知道这什么意思啊,以前根本没听过啊,但是这真真实实的出现在了redis的代码中了,刚刚开始以为这也是属于普通的队列嘛,就把他分在了struct包里了。好来分析完了,与原本我所想的差太大了。sparkline英文中的意思“微线图”,这么说吧,类似于折线图,由一个一个信息点构成。所以看到这个意思,你或许就明白了sparkline.c是干什么用的了吧,就是画图用的。我们看看这个画图的内部结构是什么,画图需要的元素是哪些:
/* 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
/* sparkline是一类信息体积小和数据密度高的图表。目前它被用作一些测量,
*相关的变化的信息呈现的方式,如平均温度,股市交投活跃。sparkline常常以一组多条的形式出现在柱状图,折线图当中。
*可以理解为一个图线信息 */
/* A sequence is represented of many "samples" */
/* 可以理解为图像上的一个信息点,有文字,有值的大小 */
struct sample {
double value;
char *label;
};
/* 图线信息结构体,包括n个元素点,可以据此描述出图,绘图的可不是直接按点和值直接绘制的 */
struct sequence {
//当前元素点个数
int length;
//总共的文字个数,有些点没有label描述,为NULL
int labels;
//元素点列表
struct sample *samples;
//元素中的最大值,最小值
double min, max;
};
/* 定义了一些渲染图时候一些属性操作设置 */
#define SPARKLINE_NO_FLAGS 0
#define SPARKLINE_FILL 1 /* Fill the area under the curve. */
#define SPARKLINE_LOG_SCALE 2 /* Use logarithmic scale. */
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 */
我们看到上面的sample结构体其实“信息点”元素的意思了,里面很简单,一个文字label,一个value值,简洁明了,然后sequence就自然是图线了,里面就定义了元素点列表了,里面还有线的长度,和最大值,最小值,信息还挺全面的线。后序的画图操作都是根据这个图线序列结构体操作的。结构体一点都不复杂。但是画图的实现一点都不简单,如何根据给定的一些点信息画出一个类似折线的图线呢,可别忘了,这是要在命令行窗口的图线哦,所以不会像高级语言中的GUI的操作那样很方便,我们看看redis代码中是怎么写的。
/* 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.
*
* ---------------------------------------------------------------------------
在sparkline.c中的注释声明,此代码修改自 http://github.com/antirez/aspark,原来是开源的代码实现,但是一开始真的不知道还有这么个叫aspark的东西,都跟BigData里的spark搞混了,然后我点击此地址,官方解释来了:
aspark is a C program to display ASCII Sparklines.
It is completely useless in 2011.
不错,意思就是说aspark就是用来在C程序上显示图线效果的。后来,我看了下,的确代码差不多,redis的代码在上面加了自己的东西,稍稍修改,aspark的图线展现有几种形式,第一种,最简单的展示:
$ ./aspark 1,2,3,4,10,7,6,5 `-_ __-` `第二张把行数扩展为更多行,展示更多的数据,上面的这个为2行展示,数据多的时候,调节行数,默认输出2行展示
$ ./aspark 1,2,3,4,5,6,7,8,9,10,10,8,5,3,1 --rows 4 _-``_ _` -` ` _-` `_
$ ./aspark 1,2,3,4,5,6,7,8,9,10,10,8,5,3,1 --rows 4 --fill _o##_ _#||||| o#|||||||# _o#||||||||||#_用了"|"符号,很有想象力的哦,最最关键的我们看如何实现这样的效果呢,核心代码如下:
/* 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;
int steps = charset_len*rows;
int row = 0;
char *chars = zmalloc(len);
int loop = 1;
int opt_fill = flags & SPARKLINE_FILL;
int opt_log = flags & SPARKLINE_LOG_SCALE;
if (opt_log) {
relmax = log(relmax+1);
} else if (relmax == 0) {
relmax = 1;
}
while(loop) {
loop = 0;
memset(chars,' ',len);
for (j = 0; j < len; j++) {
struct sample *s = &seq->samples[j+offset];
//value派上用处了
double relval = s->value - seq->min;
int step;
if (opt_log) relval = log(relval+1);
//最后会算出相关的step
step = (int) (relval*steps)/relmax;
if (step < 0) step = 0;
if (step >= steps) step = steps-1;
if (row < rows) {
/* Print the character needed to create the sparkline */
/* step控制输出的字符是哪一个 */
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 {
/* Labels spacing */
if (seq->labels && row-rows < label_margin_top) {
loop = 1;
break;
}
/* Print the label if needed. */
if (s->label) {
int label_len = strlen(s->label);
int label_char = row - rows - label_margin_top;
if (label_len > label_char) {
loop = 1;
chars[j] = s->label[label_char];
}
}
}
}
if (loop) {
row++;
output = sdscatlen(output,chars,len);
output = sdscatlen(output,"\n",1);
}
}
zfree(chars);
return output;
}
由于本人能力有限,有点不太懂里面的具体细节,大概看了下,把变量用到的地方稍稍看了下,上面的代码都是非常优秀的代码,值得我们学习,今天至少让我知道了什么叫sparkline叫什么 了,哈哈。