1 /* sparkline.c -- ASCII Sparklines 2 * This code is modified from http://github.com/antirez/aspark and adapted 3 * in order to return SDS strings instead of outputting directly to 4 * the terminal. 5 * 6 * --------------------------------------------------------------------------- 7 * 8 * Copyright(C) 2011-2014 Salvatore Sanfilippo <[email protected]> 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions are met: 13 * 14 * * Redistributions of source code must retain the above copyright notice, 15 * this list of conditions and the following disclaimer. 16 * * Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include "redis.h" 34 35 #include <math.h> 36 37 /* This is the charset used to display the graphs, but multiple rows are used 38 * to increase the resolution. */ 39 static char charset[] = "_-`"; 40 static char charset_fill[] = "_o#"; 41 static int charset_len = sizeof(charset)-1; 42 static int label_margin_top = 1; 43 44 /* ---------------------------------------------------------------------------- 45 * Sequences are arrays of samples we use to represent data to turn 46 * into sparklines. This is the API in order to generate a sparkline: 47 * 48 * struct sequence *seq = createSparklineSequence(); 49 * sparklineSequenceAddSample(seq, 10, NULL); 50 * sparklineSequenceAddSample(seq, 20, NULL); 51 * sparklineSequenceAddSample(seq, 30, "last sample label"); 52 * sds output = sparklineRender(seq, 80, 4); 53 * freeSparklineSequence(seq); 54 * ------------------------------------------------------------------------- */ 55 56 /* Create a new sequence. */ 57 struct sequence *createSparklineSequence(void) { 58 struct sequence *seq = zmalloc(sizeof(*seq)); 59 seq->length = 0; 60 seq->samples = NULL; 61 return seq; 62 } 63 64 /* Add a new sample into a sequence. */ 65 void sparklineSequenceAddSample(struct sequence *seq, double value, char *label) { 66 if (seq->length == 0) { 67 seq->min = seq->max = value; 68 } else { 69 if (value < seq->min) seq->min = value; 70 else if (value > seq->max) seq->max = value; 71 } 72 seq->samples = zrealloc(seq->samples,sizeof(struct sample)*(seq->length+1)); 73 seq->samples[seq->length].value = value; 74 seq->samples[seq->length].label = label; 75 seq->length++; 76 if (label) seq->labels++; 77 } 78 79 /* Free a sequence. */ 80 void freeSparklineSequence(struct sequence *seq) { 81 int j; 82 83 for (j = 0; j < seq->length; j++) 84 zfree(seq->samples[j].label); 85 zfree(seq->samples); 86 zfree(seq); 87 } 88 89 /* ---------------------------------------------------------------------------- 90 * ASCII rendering of sequence 91 * ------------------------------------------------------------------------- */ 92 93 /* Render part of a sequence, so that render_sequence() call call this function 94 * with differnent parts in order to create the full output without overflowing 95 * the current terminal columns. */ 96 sds sparklineRenderRange(sds output, struct sequence *seq, int rows, int offset, int len, int flags) { 97 int j; 98 double relmax = seq->max - seq->min; 99 int steps = charset_len*rows; 100 int row = 0; 101 char *chars = zmalloc(len); 102 int loop = 1; 103 int opt_fill = flags & SPARKLINE_FILL; 104 int opt_log = flags & SPARKLINE_LOG_SCALE; 105 106 if (opt_log) { 107 relmax = log(relmax+1); 108 } else if (relmax == 0) { 109 relmax = 1; 110 } 111 112 while(loop) { 113 loop = 0; 114 memset(chars,' ',len); 115 for (j = 0; j < len; j++) { 116 struct sample *s = &seq->samples[j+offset]; 117 double relval = s->value - seq->min; 118 int step; 119 120 if (opt_log) relval = log(relval+1); 121 step = (int) (relval*steps)/relmax; 122 if (step < 0) step = 0; 123 if (step >= steps) step = steps-1; 124 125 if (row < rows) { 126 /* Print the character needed to create the sparkline */ 127 int charidx = step-((rows-row-1)*charset_len); 128 loop = 1; 129 if (charidx >= 0 && charidx < charset_len) { 130 chars[j] = opt_fill ? charset_fill[charidx] : 131 charset[charidx]; 132 } else if(opt_fill && charidx >= charset_len) { 133 chars[j] = '|'; 134 } 135 } else { 136 /* Labels spacing */ 137 if (seq->labels && row-rows < label_margin_top) { 138 loop = 1; 139 break; 140 } 141 /* Print the label if needed. */ 142 if (s->label) { 143 int label_len = strlen(s->label); 144 int label_char = row - rows - label_margin_top; 145 146 if (label_len > label_char) { 147 loop = 1; 148 chars[j] = s->label[label_char]; 149 } 150 } 151 } 152 } 153 if (loop) { 154 row++; 155 output = sdscatlen(output,chars,len); 156 output = sdscatlen(output,"\n",1); 157 } 158 } 159 zfree(chars); 160 return output; 161 } 162 163 /* Turn a sequence into its ASCII representation */ 164 sds sparklineRender(sds output, struct sequence *seq, int columns, int rows, int flags) { 165 int j; 166 167 for (j = 0; j < seq->length; j += columns) { 168 int sublen = (seq->length-j) < columns ? (seq->length-j) : columns; 169 170 if (j != 0) output = sdscatlen(output,"\n",1); 171 output = sparklineRenderRange(output, seq, rows, j, sublen, flags); 172 } 173 return output; 174 } 175 176