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 "server.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(sdsempty(), seq, 80, 4, SPARKLINE_FILL); 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 label = (label == NULL || label[0] == '\0') ? NULL : zstrdup(label); 67 if (seq->length == 0) { 68 seq->min = seq->max = value; 69 } else { 70 if (value < seq->min) seq->min = value; 71 else if (value > seq->max) seq->max = value; 72 } 73 seq->samples = zrealloc(seq->samples,sizeof(struct sample)*(seq->length+1)); 74 seq->samples[seq->length].value = value; 75 seq->samples[seq->length].label = label; 76 seq->length++; 77 if (label) seq->labels++; 78 } 79 80 /* Free a sequence. */ 81 void freeSparklineSequence(struct sequence *seq) { 82 int j; 83 84 for (j = 0; j < seq->length; j++) 85 zfree(seq->samples[j].label); 86 zfree(seq->samples); 87 zfree(seq); 88 } 89 90 /* ---------------------------------------------------------------------------- 91 * ASCII rendering of sequence 92 * ------------------------------------------------------------------------- */ 93 94 /* Render part of a sequence, so that render_sequence() call call this function 95 * with differnent parts in order to create the full output without overflowing 96 * the current terminal columns. */ 97 sds sparklineRenderRange(sds output, struct sequence *seq, int rows, int offset, int len, int flags) { 98 int j; 99 double relmax = seq->max - seq->min; 100 int steps = charset_len*rows; 101 int row = 0; 102 char *chars = zmalloc(len); 103 int loop = 1; 104 int opt_fill = flags & SPARKLINE_FILL; 105 int opt_log = flags & SPARKLINE_LOG_SCALE; 106 107 if (opt_log) { 108 relmax = log(relmax+1); 109 } else if (relmax == 0) { 110 relmax = 1; 111 } 112 113 while(loop) { 114 loop = 0; 115 memset(chars,' ',len); 116 for (j = 0; j < len; j++) { 117 struct sample *s = &seq->samples[j+offset]; 118 double relval = s->value - seq->min; 119 int step; 120 121 if (opt_log) relval = log(relval+1); 122 step = (int) (relval*steps)/relmax; 123 if (step < 0) step = 0; 124 if (step >= steps) step = steps-1; 125 126 if (row < rows) { 127 /* Print the character needed to create the sparkline */ 128 int charidx = step-((rows-row-1)*charset_len); 129 loop = 1; 130 if (charidx >= 0 && charidx < charset_len) { 131 chars[j] = opt_fill ? charset_fill[charidx] : 132 charset[charidx]; 133 } else if(opt_fill && charidx >= charset_len) { 134 chars[j] = '|'; 135 } 136 } else { 137 /* Labels spacing */ 138 if (seq->labels && row-rows < label_margin_top) { 139 loop = 1; 140 break; 141 } 142 /* Print the label if needed. */ 143 if (s->label) { 144 int label_len = strlen(s->label); 145 int label_char = row - rows - label_margin_top; 146 147 if (label_len > label_char) { 148 loop = 1; 149 chars[j] = s->label[label_char]; 150 } 151 } 152 } 153 } 154 if (loop) { 155 row++; 156 output = sdscatlen(output,chars,len); 157 output = sdscatlen(output,"\n",1); 158 } 159 } 160 zfree(chars); 161 return output; 162 } 163 164 /* Turn a sequence into its ASCII representation */ 165 sds sparklineRender(sds output, struct sequence *seq, int columns, int rows, int flags) { 166 int j; 167 168 for (j = 0; j < seq->length; j += columns) { 169 int sublen = (seq->length-j) < columns ? (seq->length-j) : columns; 170 171 if (j != 0) output = sdscatlen(output,"\n",1); 172 output = sparklineRenderRange(output, seq, rows, j, sublen, flags); 173 } 174 return output; 175 } 176 177