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