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 different 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 | |