xref: /redis-3.2.3/src/sparkline.c (revision cef054e8)
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. */
createSparklineSequence(void)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. */
sparklineSequenceAddSample(struct sequence * seq,double value,char * label)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. */
freeSparklineSequence(struct sequence * seq)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. */
sparklineRenderRange(sds output,struct sequence * seq,int rows,int offset,int len,int flags)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 */
sparklineRender(sds output,struct sequence * seq,int columns,int rows,int flags)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