/** * Utilities for the quality of service module mod_qos. * * qspng.c: Tool to draw graph from qslog output. * * See http://mod-qos.sourceforge.net/ for further * details. * * Copyright (C) 2023 Pascal Buchbinder * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ static const char revision[] = "$Id: qspng.c 2654 2022-05-13 09:12:42Z pbuchbinder $"; #include #include #include #include #include //#include #include "qs_util.h" #include "char.h" #define HUGE_STRING_LEN 1024 #define X_SAMPLE_RATE 3 /* width */ #define X_COUNTS 60 * 24 / X_SAMPLE_RATE // 24 hours, every 3th sample /* height */ #define Y_COUNTS 100 /* border */ #define XY_BORDER 20 typedef struct { const char* param; const char* name; int r; int g; int b; } qs_png_elt_t; /* known graph types */ static const qs_png_elt_t qs_png_elts[] = { { "r/s", "requests per second", 20, 30, 130, }, { "req", "requests per minute", 20, 30, 130, }, { "b/s", "bytes per second (out)", 30, 45, 130 }, { "ib/s", "bytes per second (in)", 30, 45, 125 }, { "esco", "established connections per minute", 40, 95, 140 }, { "av", "average response time", 40, 95, 140 }, { "avms", "average response time in milliseconds", 45, 95, 135 }, { "0-49ms", "requests duration 0-49ms", 45, 100, 180 }, { "50-99ms", "requests duration 50-99ms", 45, 100, 180 }, { "100-499ms", "requests duration 100-499ms", 45, 100, 180 }, { "500-999ms", "requests duration 500-999ms", 45, 100, 180 }, { "<1s", "requests faster than 1 second", 35, 95, 180 }, { "1s", "requests faster or equal than 1 second", 35, 90, 180 }, { "2s", "requests with 2 seconds response time", 30, 85, 180 }, { "3s", "requests with 3 seconds response time", 25, 90, 180 }, { "4s", "requests with 4 seconds response time", 25, 95, 180 }, { "5s", "requests with 5 seconds response time", 15, 90, 180 }, { ">5s","requests slower than 5 seconds", 35, 90, 185 }, { "1xx","requests with HTTP status 1xx", 50, 70, 150 }, { "2xx","requests with HTTP status 2xx", 50, 70, 150 }, { "3xx","requests with HTTP status 3xx", 50, 70, 150 }, { "4xx","requests with HTTP status 4xx", 50, 70, 150 }, { "5xx","requests with HTTP status 5xx", 50, 70, 150 }, { "ip", "IP addresses", 55, 60, 150 }, { "usr","active users", 55, 66, 150 }, { "qV", "created VIP sessions", 55, 50, 155 }, { "qS", "session pass", 55, 75, 160 }, { "qD", "access denied", 55, 70, 170 }, { "qK", "connection closed", 55, 60, 145 }, { "qT", "dynamic keep-alive", 55, 55, 153 }, { "qL", "slow down", 55, 65, 140 }, { "qA", "connection aborts", 55, 50, 175 }, { "qs", "serialization", 55, 40, 175 }, { "qu", "start user tracking", 55, 45, 175 }, { "sl", "system load", 25, 60, 175 }, { "m", "free memory", 35, 90, 185 }, { NULL, NULL, 0, 0, 0 } }; typedef struct qs_png_conf_st { char *path; char *param; } qs_png_conf; /************************************************************************ * Functions ***********************************************************************/ /** * Read the stat_log data line by line * * @param s IN buffer to store line to * @param n IN buffer size * @param f IN file descriptor * * @return 1 on EOF, else 0 */ static int qs_png_getline(char *s, int n, FILE *f) { register int i = 0; while (1) { s[i] = (char) fgetc(f); if (s[i] == CR) { s[i] = fgetc(f); } if ((s[i] == 0x4) || (s[i] == LF) || (i == (n - 1))) { s[i] = '\0'; return (feof(f) ? 1 : 0); } ++i; } } /* png io callback (should write to buff/bio/bucket when using in apache) */ void lp_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { FILE *f = png_get_io_ptr(png_ptr); fwrite(data, length, 1, f); } /* png io callback (not used) */ void lp_flush_data(png_structp png_ptr) { png_get_io_ptr(png_ptr); fprintf(stderr, "flush\n"); } /** * Writes a single char to the graph * * @param x IN x position * @param y IN y position * @param row_pointers IN start pointer (0/0) * @param n IN char to write */ static void qs_png_write_char(int x, int y, png_bytep *row_pointers, char n) { int ix, iy; int *f = &s_X[0][0]; switch(n) { case 'a': f = &s_a[0][0]; break; case 'b': f = &s_b[0][0]; break; case 'c': f = &s_c[0][0]; break; case 'd': f = &s_d[0][0]; break; case 'e': f = &s_e[0][0]; break; case 'f': f = &s_f[0][0]; break; case 'g': f = &s_g[0][0]; break; case 'h': f = &s_h[0][0]; break; case 'i': f = &s_i[0][0]; break; case 'j': f = &s_j[0][0]; break; case 'k': f = &s_k[0][0]; break; case 'l': f = &s_l[0][0]; break; case 'm': f = &s_m[0][0]; break; case 'n': f = &s_n[0][0]; break; case 'o': f = &s_o[0][0]; break; case 'p': f = &s_p[0][0]; break; case 'q': f = &s_q[0][0]; break; case 'r': f = &s_r[0][0]; break; case 's': f = &s_s[0][0]; break; case 't': f = &s_t[0][0]; break; case 'u': f = &s_u[0][0]; break; case 'v': f = &s_v[0][0]; break; case 'w': f = &s_w[0][0]; break; case 'x': f = &s_x[0][0]; break; case 'y': f = &s_y[0][0]; break; case 'z': f = &s_z[0][0]; break; case ' ': f = &s_SP[0][0]; break; case '_': f = &s_US[0][0]; break; case '(': f = &s_BRO[0][0]; break; case ')': f = &s_BRC[0][0]; break; case '<': f = &s_LT[0][0]; break; case '>': f = &s_GT[0][0]; break; case '-': f = &s_MI[0][0]; break; case '/': f = &s_SL[0][0]; break; case ';': f = &s_SC[0][0]; break; case ',': f = &s_CM[0][0]; break; case ':': f = &s_CO[0][0]; break; case '.': f = &s_DT[0][0]; break; case '\'': f = &s_SQ[0][0]; break; case 'A': f = &s_a[0][0]; break; case 'B': f = &s_b[0][0]; break; case 'C': f = &s_c[0][0]; break; case 'D': f = &s_d[0][0]; break; case 'E': f = &s_e[0][0]; break; case 'F': f = &s_f[0][0]; break; case 'G': f = &s_g[0][0]; break; case 'H': f = &s_h[0][0]; break; case 'I': f = &s_i[0][0]; break; case 'J': f = &s_j[0][0]; break; case 'K': f = &s_k[0][0]; break; case 'L': f = &s_l[0][0]; break; case 'M': f = &s_M[0][0]; break; case 'N': f = &s_n[0][0]; break; case 'O': f = &s_o[0][0]; break; case 'P': f = &s_p[0][0]; break; case 'Q': f = &s_q[0][0]; break; case 'R': f = &s_r[0][0]; break; case 'S': f = &s_s[0][0]; break; case 'T': f = &s_t[0][0]; break; case 'U': f = &s_u[0][0]; break; case 'V': f = &s_v[0][0]; break; case 'W': f = &s_w[0][0]; break; case 'X': f = &s_x[0][0]; break; case 'Y': f = &s_y[0][0]; break; case 'Z': f = &s_z[0][0]; break; case '0': f = &s_0[0][0]; break; case '1': f = &s_1[0][0]; break; case '2': f = &s_2[0][0]; break; case '3': f = &s_3[0][0]; break; case '4': f = &s_4[0][0]; break; case '5': f = &s_5[0][0]; break; case '6': f = &s_6[0][0]; break; case '7': f = &s_7[0][0]; break; case '8': f = &s_8[0][0]; break; case '9': f = &s_9[0][0]; break; } /* print the char matrix */ for(iy = 0; iy < S_H_MAX; iy++) { png_byte* row = row_pointers[y+iy]; for(ix = 0; ix < S_W_MAX; ix++) { png_byte* ptr = &(row[(x+ix)*4]); if(f[iy*S_W_MAX + ix] == 1) { /* foreground */ ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; } else { /* background */ ptr[0] = 250; ptr[1] = 250; ptr[2] = 255; } } } } /** * Writes a single digit 0..9. * You should normally use either qs_png_write_int() or qs_png_write_int(). * * @param x IN x position * @param y IN y position * @param row_pointers IN start pointer (0/0) * @param n IN number to write */ static void qs_png_write_digit(int x, int y, png_bytep *row_pointers, int n) { char f = 'X'; if(n == 0) f = '0'; if(n == 1) f = '1'; if(n == 2) f = '2'; if(n == 3) f = '3'; if(n == 4) f = '4'; if(n == 5) f = '5'; if(n == 6) f = '6'; if(n == 7) f = '7'; if(n == 8) f = '8'; if(n == 9) f = '9'; qs_png_write_char(x, y, row_pointers, f); } /** * Writes a string to the graph. * * @param x IN x position * @param y IN y position * @param row_pointers IN start pointer (0/0) * @param n IN string to write */ static void qs_png_write_string(int x, int y, png_bytep *row_pointers, const char *n) { int i = 0; int offset = 0; while(n[i] != '\0') { qs_png_write_char(x+offset, y, row_pointers, n[i]); i++; offset = offset + S_W_MAX; } } /** * Writes a number (int) to the graph (1:1). * * @param x IN x position * @param y IN y position * @param row_pointers IN start pointer (0/0) * @param n IN number to write */ static void qs_png_write_int(int x, int y, png_bytep *row_pointers, int n) { char num_str[HUGE_STRING_LEN]; snprintf(num_str, sizeof(num_str), "%d", n); qs_png_write_string(x, y, row_pointers, num_str); } /** * Writes a number (long) to the graph using k,M for big numbers. * * @param x IN x position * @param y IN y position * @param row_pointers IN start pointer (0/0) * @param n IN string to write */ static void qs_png_write_long(int x, int y, png_bytep *row_pointers, long n) { char num_str[HUGE_STRING_LEN]; snprintf(num_str, sizeof(num_str), "%ld", n); if(n >= 1000) { snprintf(num_str, sizeof(num_str), "%ldk", n/1000); } if(n >= 1000000) { snprintf(num_str, sizeof(num_str), "%ldM", n/1000000); } qs_png_write_string(x, y, row_pointers, num_str); } /** * Labels the graph (min,max,title). * * @param width IN size (x axis) of the graph * @param height IN size (y axis) of the graph * @param border IN border size around the graph * @param row_pointers IN start pointer (0/0) * @param max IN max y value * @param name IN title */ static void qs_png_label(int width, int height, int border, png_bytep *row_pointers, long max, const char *name) { /* MAX */ int i; int step = height/5; int c = 5; for(i = 0; i < height; i = i + step) { qs_png_write_long(1, border - (S_W_MAX/2) + i, row_pointers, max/5*c); c--; } /* MIN */ qs_png_write_int(1, height + border - (S_W_MAX/2), row_pointers, 0); /* title */ { char buf[HUGE_STRING_LEN]; snprintf(buf, sizeof(buf), "%s", name); qs_png_write_string(XY_BORDER, XY_BORDER/2-S_H_MAX/2, row_pointers, buf); } } static void lp_init(int width, int height, int border, png_bytep **start) { png_bytep *row_pointers; int b_width = width + (2 * border); int b_height = height + (2 * border); int x, y; /* alloc memory */ row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * b_height); for(y=0; y 8)) { char *e; p=p+strlen(name); e = strchr(p,';'); if(e) e[0] = '\0'; e = strchr(p, '.'); /** sl uses fp value */ if(e) e[0] = '\0'; tmp[sample-1] = atol(p); } else { tmp[sample-1] = 0; } /* hour (stat_log time format: %d.%m.%Y %H:%M:%S (19 char)) */ p = strchr(line, ';'); if(p && (p-line == 19 )) { p = p - 6; p[0] = '\0'; p = p - 2; hours[i] = atoi(p); } /* use the defined sample rate */ if(sample == X_SAMPLE_RATE) { int j; int max_value = 0; for(j = 0; j < X_SAMPLE_RATE; j++) { req[i] = req[i] + tmp[j]; if(max_value < tmp[j]) { max_value = tmp[j]; } } max_req[i] = max_value; if(max_req[i] > peak) peak = max_req[i]; /* build average */ req[i] = req[i] / X_SAMPLE_RATE; sample = 1; i++; /* and store the current date (%d.%m.%Y (10 char)) if the first value is at 00h */ if(hours[i] == 0 && i == 1) { p = strchr(line, ' '); if(p && (p-line == 10)) { p[0] = '\0'; strcpy(date_str, line); } } } else { sample++; } } /* calculate y axis scaling (1:1 are height pixels) */ if(peak < 10) { scale = 0.1; } else { while((peak / scale) > height) { if(scale < 8) { scale = scale * 2; } else { if(scale == 8) { scale = 10; } else { scale = scale * 10; } } } } /* draw the curve */ for(x=0; x -p -o [-10]\n", man ? "" : "Usage: ", cmd); printf("\n"); if(man) { printf(".SH DESCRIPTION\n"); } else { printf("Summary\n"); } qs_man_print(man, "%s is a tool to generate png (portable network graphics)\n", cmd); qs_man_print(man, "raster images files from semicolon separated data generated by the\n"); qs_man_print(man, "qslog utility. It reads up to the first 1440 entries (24 hours)\n"); qs_man_print(man, "and prints a graph using the values defined by the 'parameter' \n"); qs_man_print(man, "name.\n"); printf("\n"); if(man) { printf(".SH OPTIONS\n"); } else { printf("Options\n"); } if(man) printf(".TP\n"); qs_man_print(man, " -i \n"); if(man) printf("\n"); qs_man_print(man, " Input file to read data from.\n"); if(man) printf("\n.TP\n"); qs_man_print(man, " -p \n"); if(man) printf("\n"); qs_man_print(man, " Parameter name, e.g. r/s or usr.\n"); if(man) printf("\n.TP\n"); qs_man_print(man, " -o \n"); if(man) printf("\n"); qs_man_print(man, " Output file name, e.g. stat.png.\n"); printf("\n"); if(man) { printf(".SH SEE ALSO\n"); printf("qsdt(1), qsexec(1), qsfilter2(1), qsgeo(1), qsgrep(1), qshead(1), qslogger(1), qslog(1), qsre(1), qsrespeed(1), qsrotate(1), qssign(1), qstail(1)\n"); printf(".SH AUTHOR\n"); printf("Pascal Buchbinder, http://mod-qos.sourceforge.net/\n"); } else { printf("\n"); printf("See http://mod-qos.sourceforge.net/ for further details.\n"); } if(man) { exit(0); } else { exit(1); } } int main(int argc, char **argv) { int y; int width, height, b_width, b_height; png_byte color_type; png_byte bit_depth; int scale; png_structp png_ptr; png_infop info_ptr; png_bytep *row_pointers; char *infile = NULL; FILE *f; FILE *stat_log; char *cmd = strrchr(argv[0], '/'); const char *param = NULL; const char *name = ""; char *out = NULL; int c_r = 20; int c_g = 50; int c_b = 175; const qs_png_elt_t* elt; if(cmd == NULL) { cmd = argv[0]; } else { cmd++; } while(argc >= 1) { if(strcmp(*argv,"-i") == 0) { if (--argc >= 1) { infile = *(++argv); } } else if(strcmp(*argv,"-p") == 0) { if (--argc >= 1) { param = *(++argv); name = param; } } else if(strcmp(*argv,"-o") == 0) { if (--argc >= 1) { out = *(++argv); } } else if(strcmp(*argv,"-h") == 0) { usage(cmd, 0); } else if(strcmp(*argv,"--help") == 0) { usage(cmd, 0); } else if(strcmp(*argv,"-?") == 0) { usage(cmd, 0); } else if(strcmp(*argv,"--man") == 0) { usage(cmd, 1); } argc--; argv++; } if(infile == NULL || param == NULL || out == NULL) usage(cmd, 0); for(elt = qs_png_elts; elt->param != NULL ; ++elt) { if(strcmp(elt->param, param) == 0) { name = elt->name; c_r = elt->r; c_g = elt->g; c_b = elt->b; } } stat_log = fopen(infile, "r"); if(stat_log == NULL) { fprintf(stderr,"[%s]: ERROR, could not open input file <%s>\n", cmd, infile); exit(1); } f = fopen(out, "wb"); if(f == NULL) { fprintf(stderr,"[%s]: ERROR, could not open output file <%s>\n", cmd, out); exit(1); } png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if(png_ptr == NULL) { fprintf(stderr,"[%s]: ERROR, could not create png struct\n", cmd); exit(1); } info_ptr = png_create_info_struct(png_ptr); if(info_ptr == NULL) { fprintf(stderr,"[%s]: ERROR, could not create png information struct\n", cmd); exit(1); } if(setjmp(png_jmpbuf(png_ptr))) { fprintf(stderr,"[%s]: ERROR, could not init png struct\n", cmd); exit(1); } png_set_write_fn(png_ptr, f, lp_write_data, NULL); /* write header */ if(setjmp(png_jmpbuf(png_ptr))) { fprintf(stderr,"[%s]: ERROR, could not write png header\n", cmd); exit(1); } color_type = PNG_COLOR_TYPE_RGB_ALPHA; bit_depth = 8; width = X_COUNTS; height = Y_COUNTS; b_width = width + (2 * XY_BORDER); b_height = height + (2 * XY_BORDER); png_set_IHDR(png_ptr, info_ptr, b_width, b_height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_ptr, info_ptr); /* write bytes */ if(setjmp(png_jmpbuf(png_ptr))) { fprintf(stderr,"[%s]: ERROR, could not write png data\n", cmd); exit(1); } /* alloc and background */ lp_init(width, height, XY_BORDER, &row_pointers); /* paint */ { char buf[HUGE_STRING_LEN]; snprintf(buf, sizeof(buf), ";%s;", param); scale = qs_png_draw(width, height, XY_BORDER, row_pointers, stat_log, buf, c_r, c_g, c_b); } /* min/max/title label */ qs_png_label(width, height, XY_BORDER, row_pointers, scale, name); /* done, write image */ png_write_image(png_ptr, row_pointers); /* end write */ if(setjmp(png_jmpbuf(png_ptr))) { fprintf(stderr,"[%s]: ERROR, could not write png data\n", cmd); exit(1); } png_write_end(png_ptr, NULL); /* cleanup heap allocation */ for(y=0; y