redis源码sparkline详解

  • sparkline功能介绍
  • sparkline实现方法-代码解析
  • sparkline.h文件解析
  • sparkline.c文件解析
  • OVER


sparkline功能介绍

当我第一次打开sparkline源文件的时候我的表情是这样的

Sparklines sparkLine是什么意思_折线图


就像我正津津有味地看着汪汪队呢,突然换了台,换到了相亲节目。这是在干啥呢??

Sparklines sparkLine是什么意思_折线_02


  进入主题,先说功能:redis中sparkline功能是给定一批值 + 值对应的标签,以字符作为像素点 在窗口上将这种值和标签的对应关系表现出来,用字符生成折线图。

  正常情况下的折线图如下

Sparklines sparkLine是什么意思_redis_03


  sparkline下的折线图长下面这个样子(无填充,无标签)。

Sparklines sparkLine是什么意思_redis_04


  或者有填充的话是下面这个样子(有填充,无标签)。

Sparklines sparkLine是什么意思_spark_05


  再或者有标签的情况下是这个样子。

Sparklines sparkLine是什么意思_Sparklines_06

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