xref: /redis-3.2.3/src/sparkline.c (revision 82154ffa)
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