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. */
39static char charset[] = "_-`";
40static char charset_fill[] = "_o#";
41static int charset_len = sizeof(charset)-1;
42static 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. */
57struct 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. */
65void 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. */
81void 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 different parts in order to create the full output without overflowing
96 * the current terminal columns. */
97sds 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 */
165sds 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