1 /*
2 * Copyright (c) 2016-2021, Circonus, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <math.h>
21 #include <getopt.h>
22 #include <inttypes.h>
23 #include <stdbool.h>
24 #include "circllhist.h"
25
26 bool cumulative = false;
27
help(const char * prog)28 void help(const char *prog) {
29 printf("%s:\n", prog);
30 printf("\t-h\t\thelp\n");
31 printf("\t-a <val>\tcompute number of samples above <val>\n");
32 printf("\t-b <val>\tcompute number of samples below <val>\n");
33 printf("\t-p <0-100>\tcompute approximate percentile\n");
34 printf("\t-i <val>\tcompute approximate inverse quantile at <val>\n");
35 printf("\t-C\t\tcalculate difference between cumulative histograms\n");
36 printf("\t[hist1 [hist2 [...]]]\n\n");
37 printf("If no hists are specified, stdin is read\n");
38 printf("\n\nExample:\n\n");
39 printf("\tcurl url |\n\t jq -r .stat._value |\n\t %s -i 0.2 -p 0 -p 100 -p 99 -p 99.999 -p 50\n\n", prog);
40 printf("\tThis will fetch the document at URL, extract the b64 encoded histogram\n");
41 printf("\tprinting the inverse quantile at 0.2 (seconds)\n");
42 printf("\tand the p0, p50, p99, p99.999, and p100\n");
43 }
44
print_hist(const histogram_t * hist)45 void print_hist(const histogram_t *hist) {
46 printf("{");
47 int cnt = hist_bucket_count(hist);
48 for(int i=0; i<cnt; i++) {
49 double v;
50 uint64_t vc;
51 hist_bucket_idx(hist, i, &v, &vc);
52 printf("%s\"%g\":%" PRIu64, i ? "," : "", v, vc);
53 }
54 printf("}\n");
55 }
56
decode(const char * buff)57 histogram_t *decode(const char *buff) {
58 histogram_t *hist = hist_alloc();
59 if(hist_deserialize_b64(hist, buff, strlen(buff)) <= -1) {
60 fprintf(stderr, "histogram invalid\n");
61 hist_free(hist);
62 return NULL;
63 }
64 return hist;
65 }
66
calc_cum(const histogram_t * base,const histogram_t * now,bool cumulative)67 histogram_t *calc_cum(const histogram_t *base, const histogram_t *now, bool cumulative) {
68 histogram_t *result = hist_clone(now);
69 if(cumulative && !base) return NULL;
70 if(!cumulative) return result;
71 if(hist_subtract(result, &base, 1) == 0) return result;
72 fprintf(stderr, "histogram cumulative calculation reset\n");
73 hist_free(result);
74 return NULL;
75 }
76
77 struct calcs {
78 int cnt;
79 double *elements;
80 } above = { 0 }, below = { 0 }, quantiles = { 0 }, invquantiles = { 0 };
81
add_to(struct calcs * c,double v)82 void add_to(struct calcs *c, double v) {
83 c->elements = realloc(c->elements, sizeof(double) * (1 + c->cnt));
84 c->elements[c->cnt++] = v;
85 }
86
print(const histogram_t * hist)87 void print(const histogram_t *hist) {
88 int i;
89 if(above.cnt || below.cnt || quantiles.cnt) {
90 printf("{");
91 for(i=0; i<above.cnt; i++) {
92 uint64_t cnt = hist_approx_count_above(hist, above.elements[i]);
93 printf("\"above(%g)\":%zu,", above.elements[i], (size_t)cnt);
94 }
95 for(i=0; i<below.cnt; i++) {
96 uint64_t cnt = hist_approx_count_below(hist, below.elements[i]);
97 printf("\"below(%g)\":%zu,", below.elements[i], (size_t)cnt);
98 }
99 double vals[quantiles.cnt];
100 hist_approx_quantile(hist, quantiles.elements, quantiles.cnt, vals);
101 for(i=0; i<quantiles.cnt; i++) {
102 printf("\"p(%f%%)\":%g,", quantiles.elements[i] * 100, vals[i]);
103 }
104 double qvals[invquantiles.cnt];
105 hist_approx_inverse_quantile(hist, invquantiles.elements, invquantiles.cnt, qvals);
106 for(i=0; i<invquantiles.cnt; i++) {
107 printf("\"invq(%f)\":%g,", invquantiles.elements[i], qvals[i]);
108 }
109 printf("\"count\":%zu}\n", hist_sample_count(hist));
110 }
111 else print_hist(hist);
112 }
113
double_compare(const void * lv,const void * rv)114 static int double_compare(const void *lv, const void *rv) {
115 double l = *(double *)lv;
116 double r = *(double *)rv;
117 if(l < r) return -1;
118 if(l > r) return 1;
119 return 0;
120 }
121
main(int argc,char ** argv)122 int main(int argc, char **argv) {
123 double percent;
124 char buff[256*1024];
125 int opt;
126 while((opt = getopt(argc, argv, "ha:b:p:i:C")) != -1) {
127 switch(opt) {
128 case 'a':
129 add_to(&above, atof(optarg));
130 break;
131 case 'b':
132 add_to(&below, atof(optarg));
133 break;
134 case 'i':
135 add_to(&invquantiles, atof(optarg));
136 break;
137 case 'p':
138 percent = atof(optarg);
139 if(percent < 0 || percent > 100) {
140 fprintf(stderr, "Invalid percentile %f\n", percent);
141 exit(-1);
142 }
143 add_to(&quantiles, percent/100);
144 break;
145 case 'C':
146 cumulative = true;
147 break;
148 case 'h':
149 help(argv[0]);
150 exit(0);
151 default:
152 fprintf(stderr, "unrecognized option: -%c\n", opt);
153 exit(-1);
154 break;
155 }
156 }
157
158 if(quantiles.cnt) qsort(quantiles.elements, quantiles.cnt, sizeof(double), double_compare);
159
160 histogram_t *last = NULL;
161 if(optind < argc) {
162 for(int i=optind; i<argc; i++) {
163 if(strlen(argv[i]) > sizeof(buff)-1) {
164 fprintf(stderr, "histogram too large\n");
165 exit(-1);
166 }
167 histogram_t *hist = decode(argv[i]);
168 if(hist) {
169 histogram_t *toprint = calc_cum(last, hist, cumulative);
170 if(toprint) print(toprint);
171 if(last) hist_free(last);
172 last = hist;
173 }
174 }
175 } else {
176 while(NULL != fgets(buff, sizeof(buff), stdin)) {
177 histogram_t *hist = decode(buff);
178 if(hist) {
179 histogram_t *toprint = calc_cum(last, hist, cumulative);
180 if(toprint) print(toprint);
181 if(last) hist_free(last);
182 last = hist;
183 }
184 }
185 }
186 if(last) hist_free(last);
187 return 0;
188 }
189