prometheus-client-mmap 0.7.0.beta30 → 0.7.0.beta31

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: baae77f6d273813515be523af6a16e159006f636
4
- data.tar.gz: 60a044917b7edb908639a22df60727d85136297f
3
+ metadata.gz: e30cce5a536c75e237464f432aaa103efd53f9af
4
+ data.tar.gz: c9d27972e59162b19c9b4a71a7d35ea4c1fc849b
5
5
  SHA512:
6
- metadata.gz: 305cf2a73f788de59de593483847ecb105c8a79b6a98a9f308d1d02a3ac6263b141ae17a546756642cb8ab1e7f9dcaa94b6ae3a9edeeaffcd86628a9ffaf3106
7
- data.tar.gz: d3df54b93d82293d7d4a66ca59811296e11b6543128fe7eb354248dd56fe977bfe071d11feb192dcc1247fc20e9dbbfbf2aa255602ff8afbf54233b6c4082684
6
+ metadata.gz: ce6c424d737eb6b5863008e5a08be2a4172ea1df4fad9eae3246746b90503b849079b77025bc2401895686580c63b7b17e64b760f51189b12b8f0b054c45c79b
7
+ data.tar.gz: bc6977b2f00f9b0a047cdf0c38c72018cde0e1682e07070d356ee4e7385e710881cd243be6978c8738cb69851635e7bdfb97b137691fdfdf2ea81ec1a500f0a6
@@ -1,5 +1,7 @@
1
1
  require 'mkmf'
2
- $CFLAGS << ' -Wall -Wextra -Werror'
2
+ $CFLAGS << ' -std=c11 -Wall -Wextra -Werror'
3
+
4
+ CONFIG['warnflags'].slice!(/ -Wdeclaration-after-statement/)
3
5
 
4
6
  extension_name = 'fast_mmaped_file'
5
7
 
@@ -10,25 +10,7 @@
10
10
  VALUE MMAPED_FILE = Qnil;
11
11
 
12
12
  #define START_POSITION 8
13
-
14
- VALUE rb_hash_has_key(VALUE hash, VALUE key);
15
-
16
- VALUE method_get_double(VALUE self, VALUE index) {
17
- mm_ipc *i_mm;
18
- GetMmap(self, i_mm, MM_MODIFY);
19
-
20
- Check_Type(index, T_FIXNUM);
21
- size_t idx = NUM2UINT(index);
22
-
23
- if ((i_mm->t->real + sizeof(double)) <= idx) {
24
- rb_raise(rb_eIndexError, "index %ld out of string", idx);
25
- }
26
-
27
- double tmp;
28
-
29
- memcpy(&tmp, (char *)i_mm->t->addr + idx, sizeof(double));
30
- return DBL2NUM(tmp);
31
- }
13
+ #define INITIAL_SIZE (2 * sizeof(int32_t))
32
14
 
33
15
  void expand(mm_ipc *i_mm, size_t len){
34
16
  int fd;
@@ -64,9 +46,46 @@ void expand(mm_ipc *i_mm, size_t len){
64
46
  i_mm->t->real = len;
65
47
  }
66
48
 
49
+ inline uint32_t padding_length(uint32_t key_length){
50
+ return 8 - (key_length + 4) % 8; // padding | 8 byte aligned
51
+ }
52
+
53
+ void save_entry(mm_ipc *i_mm, uint32_t offset, VALUE key, VALUE value){
54
+ uint32_t key_length = (uint32_t)RSTRING_LEN(key);
55
+
56
+ char *pos = (char *)i_mm->t->addr + offset;
57
+
58
+ memcpy(pos, &key_length, sizeof(uint32_t));
59
+ pos += sizeof(uint32_t);
60
+
61
+ memmove(pos, StringValuePtr(key), key_length);
62
+ pos += key_length;
63
+
64
+ memset(pos, ' ', padding_length(key_length));
65
+ pos += padding_length(key_length);
66
+
67
+ double val = NUM2DBL(value);
68
+ memcpy(pos, &val, sizeof(double));
69
+ }
70
+
71
+ inline uint32_t load_used(mm_ipc *i_mm){
72
+ uint32_t used=0;
73
+ memcpy(&used, (char *)i_mm->t->addr, sizeof(uint32_t));
74
+
75
+ if (used == 0){
76
+ used = START_POSITION;
77
+ }
78
+ return used;
79
+ }
80
+
81
+ inline void save_used(mm_ipc *i_mm, uint32_t used){
82
+ memcpy((char *)i_mm->t->addr, &used, sizeof(uint32_t));
83
+ }
84
+
67
85
  VALUE method_add_entry(VALUE self, VALUE positions, VALUE key, VALUE value) {
68
86
  Check_Type(positions, T_HASH);
69
87
  Check_Type(key, T_STRING);
88
+
70
89
  VALUE position = rb_hash_lookup(positions, key);
71
90
  if (position != Qnil){
72
91
  return position;
@@ -75,56 +94,151 @@ VALUE method_add_entry(VALUE self, VALUE positions, VALUE key, VALUE value) {
75
94
  mm_ipc *i_mm;
76
95
  GetMmap(self, i_mm, MM_MODIFY);
77
96
 
78
- if ((i_mm->t->len) < sizeof(int32_t)) {
79
- expand(i_mm, sizeof(int32_t)); // TODO: better size
97
+ if ((i_mm->t->len) < INITIAL_SIZE) {
98
+ expand(i_mm, INITIAL_SIZE);
80
99
  }
81
100
 
82
- uint32_t used = 0;
83
-
84
- memcpy(&used, (char *)i_mm->t->addr, sizeof(uint32_t));
101
+ if (i_mm->t->flag & MM_FROZEN){
102
+ rb_error_frozen("mmap");
103
+ }
85
104
 
86
- if (used == 0){
87
- used = START_POSITION;
105
+ if (RSTRING_LEN(key) > UINT32_MAX){
106
+ rb_raise(rb_eArgError, "string length gt %u", UINT32_MAX);
88
107
  }
89
108
 
90
- long slen = RSTRING_LEN(key);
91
- uint32_t padding_length = 8 - (slen + 4) % 8; // padding | 8 byte aligned
92
- //TODO: check slen if its less than uint32 throw exception otherwise
93
- uint32_t entry_length = (uint32_t)slen;
94
- uint32_t new_used = used + sizeof(uint32_t) + entry_length + padding_length + sizeof(double);
109
+ uint32_t key_length = (uint32_t)RSTRING_LEN(key);
110
+ uint32_t value_offset = sizeof(uint32_t) + key_length + padding_length(key_length);
111
+ uint32_t entry_length = value_offset + sizeof(double);
95
112
 
96
- if (i_mm->t->len < new_used) {
113
+ uint32_t used = load_used(i_mm);
114
+ while (i_mm->t->len < (used + entry_length)) {
97
115
  expand(i_mm, i_mm->t->len * 2);
98
116
  }
99
117
 
100
- //TODO: add checks if mmap can be written to
118
+ save_entry(i_mm, used, key, value);
119
+ save_used(i_mm, used + entry_length);
120
+ return rb_hash_aset(positions, key, INT2NUM(used + value_offset));
121
+ }
101
122
 
102
- char *pos = (char *)i_mm->t->addr + used;
123
+ VALUE method_get_double(VALUE self, VALUE index) {
124
+ mm_ipc *i_mm;
125
+ GetMmap(self, i_mm, MM_MODIFY);
103
126
 
104
- memcpy(pos, &entry_length, sizeof(uint32_t));
105
- pos += sizeof(uint32_t);
127
+ Check_Type(index, T_FIXNUM);
128
+ size_t idx = NUM2UINT(index);
129
+
130
+ if ((i_mm->t->real + sizeof(double)) <= idx) {
131
+ rb_raise(rb_eIndexError, "index %ld out of string", idx);
132
+ }
106
133
 
107
- memmove(pos, StringValuePtr(key), slen);
108
- pos += slen;
134
+ double tmp;
109
135
 
110
- memset(pos, ' ', padding_length);
111
- pos += padding_length;
136
+ memcpy(&tmp, (char *)i_mm->t->addr + idx, sizeof(double));
137
+ return DBL2NUM(tmp);
138
+ }
112
139
 
113
- double val = NUM2DBL(value);
114
- memcpy(pos, &val, sizeof(double));
140
+ #include <jsmn.h>
141
+
142
+ #define min(a,b) \
143
+ ({ __typeof__ (a) _a = (a); \
144
+ __typeof__ (b) _b = (b); \
145
+ _a < _b ? _a : _b; })
146
+
147
+ VALUE token_to_s(char* json, jsmntok_t *t){
148
+ int len = t->end - t->start;
149
+ if (len == 0) {
150
+ return rb_str_new_literal("");
151
+ } else {
152
+ return rb_str_new(json + t->start, t->end - t->start);
153
+ }
154
+ }
155
+
156
+ VALUE fast_json_parse(char* json, size_t json_size){
157
+ jsmn_parser parser;
158
+ jsmn_init(&parser);
115
159
 
160
+ jsmntok_t t[128];
161
+ int r = jsmn_parse(&parser, json, json_size, t, sizeof(t)/sizeof(t[0]));
116
162
 
117
- position = INT2NUM(new_used - 8);
118
- rb_hash_aset(positions, key, position);
163
+ if (r < 3) {
164
+ return Qnil;
165
+ }
166
+
167
+ VALUE labels = rb_hash_new();
168
+ int label_cnt = (r-5)/2;
169
+ //TODO: (r - 4) % 2 == 0
170
+
171
+ for(int i = 0; i < label_cnt; i++){
172
+ int key = 4 + i;
173
+ int val = 5 + label_cnt + i;
174
+ //TODO: add type checks
119
175
 
120
- memcpy((char *)i_mm->t->addr, &new_used, sizeof(uint32_t));
176
+ if (strncmp("null", json + t[val].start, min(4, t[val].end - t[val].start)) == 0) {
177
+ rb_hash_aset(labels, token_to_s(json, &t[key]), Qnil);
178
+ } else {
179
+ rb_hash_aset(labels, token_to_s(json, &t[key]), token_to_s(json, &t[val]));
180
+ }
181
+ }
121
182
 
122
- return position;
183
+ return rb_ary_new_from_args(3, token_to_s(json, &t[1]), token_to_s(json, &t[2]), labels);
123
184
  }
124
185
 
125
186
 
187
+ VALUE method_fast_json_parse(VALUE self, VALUE source){
188
+ Check_Type(source, T_STRING);
189
+ NIL_P(self);
190
+
191
+ return fast_json_parse(StringValuePtr(source), RSTRING_LEN(source));
192
+ }
193
+
194
+ VALUE method_fast_entries(VALUE self, VALUE _data){
195
+ Check_Type(_data, T_STRING);
196
+ NIL_P(self);
197
+
198
+ char *data = StringValuePtr(_data);
199
+ uint64_t data_len = RSTRING_LEN(_data);
200
+
201
+ uint32_t used;
202
+ memcpy(&used, data, sizeof(uint32_t));
203
+
204
+ uint32_t pos = START_POSITION;
205
+
206
+ while (pos < used && (pos + 2*sizeof(uint32_t)) < data_len) {
207
+ uint32_t encoded_len, first_bytes;
208
+ memcpy(&encoded_len, data + pos, sizeof(uint32_t));
209
+ pos += sizeof(uint32_t);
210
+
211
+ memcpy(&first_bytes, data + pos, sizeof(uint32_t));
212
+ if (encoded_len == 0 || first_bytes == 0){
213
+ pos += 8;
214
+ continue; // do not parse empty data
215
+ }
216
+
217
+ uint32_t value_offset = encoded_len + padding_length(encoded_len);
218
+ VALUE metric_name_labels = fast_json_parse(data + pos, encoded_len);
219
+
220
+ double value;
221
+ pos += value_offset;
222
+ memcpy(&value, data + pos, sizeof(double));
223
+
224
+ rb_yield_values(4, rb_ary_entry(metric_name_labels, 0),
225
+ rb_ary_entry(metric_name_labels, 1),
226
+ rb_ary_entry(metric_name_labels, 2),
227
+ DBL2NUM(value)
228
+ );
229
+
230
+ pos += sizeof(double);
231
+ }
232
+
233
+ return Qnil;
234
+ }
235
+
126
236
  void Init_fast_mmaped_file() {
237
+
127
238
  MMAPED_FILE = rb_define_module("FastMmapedFile");
239
+ rb_define_singleton_method(MMAPED_FILE, "fast_json_parse", method_fast_json_parse, 1);
240
+ rb_define_singleton_method(MMAPED_FILE, "fast_entries", method_fast_entries, 1);
241
+
128
242
  rb_define_method(MMAPED_FILE, "get_double", method_get_double, 1);
129
243
  rb_define_method(MMAPED_FILE, "add_entry", method_add_entry, 3);
130
244
  }
@@ -0,0 +1,313 @@
1
+ #include "jsmn.h"
2
+
3
+ /**
4
+ * Allocates a fresh unused token from the token pull.
5
+ */
6
+ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
7
+ jsmntok_t *tokens, size_t num_tokens) {
8
+ jsmntok_t *tok;
9
+ if (parser->toknext >= num_tokens) {
10
+ return NULL;
11
+ }
12
+ tok = &tokens[parser->toknext++];
13
+ tok->start = tok->end = -1;
14
+ tok->size = 0;
15
+ #ifdef JSMN_PARENT_LINKS
16
+ tok->parent = -1;
17
+ #endif
18
+ return tok;
19
+ }
20
+
21
+ /**
22
+ * Fills token type and boundaries.
23
+ */
24
+ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
25
+ int start, int end) {
26
+ token->type = type;
27
+ token->start = start;
28
+ token->end = end;
29
+ token->size = 0;
30
+ }
31
+
32
+ /**
33
+ * Fills next available token with JSON primitive.
34
+ */
35
+ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
36
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
37
+ jsmntok_t *token;
38
+ int start;
39
+
40
+ start = parser->pos;
41
+
42
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
43
+ switch (js[parser->pos]) {
44
+ #ifndef JSMN_STRICT
45
+ /* In strict mode primitive must be followed by "," or "}" or "]" */
46
+ case ':':
47
+ #endif
48
+ case '\t' : case '\r' : case '\n' : case ' ' :
49
+ case ',' : case ']' : case '}' :
50
+ goto found;
51
+ }
52
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
53
+ parser->pos = start;
54
+ return JSMN_ERROR_INVAL;
55
+ }
56
+ }
57
+ #ifdef JSMN_STRICT
58
+ /* In strict mode primitive must be followed by a comma/object/array */
59
+ parser->pos = start;
60
+ return JSMN_ERROR_PART;
61
+ #endif
62
+
63
+ found:
64
+ if (tokens == NULL) {
65
+ parser->pos--;
66
+ return 0;
67
+ }
68
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
69
+ if (token == NULL) {
70
+ parser->pos = start;
71
+ return JSMN_ERROR_NOMEM;
72
+ }
73
+ jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
74
+ #ifdef JSMN_PARENT_LINKS
75
+ token->parent = parser->toksuper;
76
+ #endif
77
+ parser->pos--;
78
+ return 0;
79
+ }
80
+
81
+ /**
82
+ * Fills next token with JSON string.
83
+ */
84
+ static int jsmn_parse_string(jsmn_parser *parser, const char *js,
85
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
86
+ jsmntok_t *token;
87
+
88
+ int start = parser->pos;
89
+
90
+ parser->pos++;
91
+
92
+ /* Skip starting quote */
93
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
94
+ char c = js[parser->pos];
95
+
96
+ /* Quote: end of string */
97
+ if (c == '\"') {
98
+ if (tokens == NULL) {
99
+ return 0;
100
+ }
101
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
102
+ if (token == NULL) {
103
+ parser->pos = start;
104
+ return JSMN_ERROR_NOMEM;
105
+ }
106
+ jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
107
+ #ifdef JSMN_PARENT_LINKS
108
+ token->parent = parser->toksuper;
109
+ #endif
110
+ return 0;
111
+ }
112
+
113
+ /* Backslash: Quoted symbol expected */
114
+ if (c == '\\' && parser->pos + 1 < len) {
115
+ int i;
116
+ parser->pos++;
117
+ switch (js[parser->pos]) {
118
+ /* Allowed escaped symbols */
119
+ case '\"': case '/' : case '\\' : case 'b' :
120
+ case 'f' : case 'r' : case 'n' : case 't' :
121
+ break;
122
+ /* Allows escaped symbol \uXXXX */
123
+ case 'u':
124
+ parser->pos++;
125
+ for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
126
+ /* If it isn't a hex character we have an error */
127
+ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
128
+ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
129
+ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
130
+ parser->pos = start;
131
+ return JSMN_ERROR_INVAL;
132
+ }
133
+ parser->pos++;
134
+ }
135
+ parser->pos--;
136
+ break;
137
+ /* Unexpected symbol */
138
+ default:
139
+ parser->pos = start;
140
+ return JSMN_ERROR_INVAL;
141
+ }
142
+ }
143
+ }
144
+ parser->pos = start;
145
+ return JSMN_ERROR_PART;
146
+ }
147
+
148
+ /**
149
+ * Parse JSON string and fill tokens.
150
+ */
151
+ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
152
+ jsmntok_t *tokens, unsigned int num_tokens) {
153
+ int r;
154
+ int i;
155
+ jsmntok_t *token;
156
+ int count = parser->toknext;
157
+
158
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
159
+ char c;
160
+ jsmntype_t type;
161
+
162
+ c = js[parser->pos];
163
+ switch (c) {
164
+ case '{': case '[':
165
+ count++;
166
+ if (tokens == NULL) {
167
+ break;
168
+ }
169
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
170
+ if (token == NULL)
171
+ return JSMN_ERROR_NOMEM;
172
+ if (parser->toksuper != -1) {
173
+ tokens[parser->toksuper].size++;
174
+ #ifdef JSMN_PARENT_LINKS
175
+ token->parent = parser->toksuper;
176
+ #endif
177
+ }
178
+ token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
179
+ token->start = parser->pos;
180
+ parser->toksuper = parser->toknext - 1;
181
+ break;
182
+ case '}': case ']':
183
+ if (tokens == NULL)
184
+ break;
185
+ type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
186
+ #ifdef JSMN_PARENT_LINKS
187
+ if (parser->toknext < 1) {
188
+ return JSMN_ERROR_INVAL;
189
+ }
190
+ token = &tokens[parser->toknext - 1];
191
+ for (;;) {
192
+ if (token->start != -1 && token->end == -1) {
193
+ if (token->type != type) {
194
+ return JSMN_ERROR_INVAL;
195
+ }
196
+ token->end = parser->pos + 1;
197
+ parser->toksuper = token->parent;
198
+ break;
199
+ }
200
+ if (token->parent == -1) {
201
+ if(token->type != type || parser->toksuper == -1) {
202
+ return JSMN_ERROR_INVAL;
203
+ }
204
+ break;
205
+ }
206
+ token = &tokens[token->parent];
207
+ }
208
+ #else
209
+ for (i = parser->toknext - 1; i >= 0; i--) {
210
+ token = &tokens[i];
211
+ if (token->start != -1 && token->end == -1) {
212
+ if (token->type != type) {
213
+ return JSMN_ERROR_INVAL;
214
+ }
215
+ parser->toksuper = -1;
216
+ token->end = parser->pos + 1;
217
+ break;
218
+ }
219
+ }
220
+ /* Error if unmatched closing bracket */
221
+ if (i == -1) return JSMN_ERROR_INVAL;
222
+ for (; i >= 0; i--) {
223
+ token = &tokens[i];
224
+ if (token->start != -1 && token->end == -1) {
225
+ parser->toksuper = i;
226
+ break;
227
+ }
228
+ }
229
+ #endif
230
+ break;
231
+ case '\"':
232
+ r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
233
+ if (r < 0) return r;
234
+ count++;
235
+ if (parser->toksuper != -1 && tokens != NULL)
236
+ tokens[parser->toksuper].size++;
237
+ break;
238
+ case '\t' : case '\r' : case '\n' : case ' ':
239
+ break;
240
+ case ':':
241
+ parser->toksuper = parser->toknext - 1;
242
+ break;
243
+ case ',':
244
+ if (tokens != NULL && parser->toksuper != -1 &&
245
+ tokens[parser->toksuper].type != JSMN_ARRAY &&
246
+ tokens[parser->toksuper].type != JSMN_OBJECT) {
247
+ #ifdef JSMN_PARENT_LINKS
248
+ parser->toksuper = tokens[parser->toksuper].parent;
249
+ #else
250
+ for (i = parser->toknext - 1; i >= 0; i--) {
251
+ if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
252
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
253
+ parser->toksuper = i;
254
+ break;
255
+ }
256
+ }
257
+ }
258
+ #endif
259
+ }
260
+ break;
261
+ #ifdef JSMN_STRICT
262
+ /* In strict mode primitives are: numbers and booleans */
263
+ case '-': case '0': case '1' : case '2': case '3' : case '4':
264
+ case '5': case '6': case '7' : case '8': case '9':
265
+ case 't': case 'f': case 'n' :
266
+ /* And they must not be keys of the object */
267
+ if (tokens != NULL && parser->toksuper != -1) {
268
+ jsmntok_t *t = &tokens[parser->toksuper];
269
+ if (t->type == JSMN_OBJECT ||
270
+ (t->type == JSMN_STRING && t->size != 0)) {
271
+ return JSMN_ERROR_INVAL;
272
+ }
273
+ }
274
+ #else
275
+ /* In non-strict mode every unquoted value is a primitive */
276
+ default:
277
+ #endif
278
+ r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
279
+ if (r < 0) return r;
280
+ count++;
281
+ if (parser->toksuper != -1 && tokens != NULL)
282
+ tokens[parser->toksuper].size++;
283
+ break;
284
+
285
+ #ifdef JSMN_STRICT
286
+ /* Unexpected char in strict mode */
287
+ default:
288
+ return JSMN_ERROR_INVAL;
289
+ #endif
290
+ }
291
+ }
292
+
293
+ if (tokens != NULL) {
294
+ for (i = parser->toknext - 1; i >= 0; i--) {
295
+ /* Unmatched opened object or array */
296
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
297
+ return JSMN_ERROR_PART;
298
+ }
299
+ }
300
+ }
301
+
302
+ return count;
303
+ }
304
+
305
+ /**
306
+ * Creates a new parser based over a given buffer with an array of tokens
307
+ * available.
308
+ */
309
+ void jsmn_init(jsmn_parser *parser) {
310
+ parser->pos = 0;
311
+ parser->toknext = 0;
312
+ parser->toksuper = -1;
313
+ }
@@ -0,0 +1,76 @@
1
+ #ifndef __JSMN_H_
2
+ #define __JSMN_H_
3
+
4
+ #include <stddef.h>
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ /**
11
+ * JSON type identifier. Basic types are:
12
+ * o Object
13
+ * o Array
14
+ * o String
15
+ * o Other primitive: number, boolean (true/false) or null
16
+ */
17
+ typedef enum {
18
+ JSMN_UNDEFINED = 0,
19
+ JSMN_OBJECT = 1,
20
+ JSMN_ARRAY = 2,
21
+ JSMN_STRING = 3,
22
+ JSMN_PRIMITIVE = 4
23
+ } jsmntype_t;
24
+
25
+ enum jsmnerr {
26
+ /* Not enough tokens were provided */
27
+ JSMN_ERROR_NOMEM = -1,
28
+ /* Invalid character inside JSON string */
29
+ JSMN_ERROR_INVAL = -2,
30
+ /* The string is not a full JSON packet, more bytes expected */
31
+ JSMN_ERROR_PART = -3
32
+ };
33
+
34
+ /**
35
+ * JSON token description.
36
+ * type type (object, array, string etc.)
37
+ * start start position in JSON data string
38
+ * end end position in JSON data string
39
+ */
40
+ typedef struct {
41
+ jsmntype_t type;
42
+ int start;
43
+ int end;
44
+ int size;
45
+ #ifdef JSMN_PARENT_LINKS
46
+ int parent;
47
+ #endif
48
+ } jsmntok_t;
49
+
50
+ /**
51
+ * JSON parser. Contains an array of token blocks available. Also stores
52
+ * the string being parsed now and current position in that string
53
+ */
54
+ typedef struct {
55
+ unsigned int pos; /* offset in the JSON string */
56
+ unsigned int toknext; /* next token to allocate */
57
+ int toksuper; /* superior token node, e.g parent object or array */
58
+ } jsmn_parser;
59
+
60
+ /**
61
+ * Create JSON parser over an array of tokens
62
+ */
63
+ void jsmn_init(jsmn_parser *parser);
64
+
65
+ /**
66
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
67
+ * a single JSON object.
68
+ */
69
+ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
70
+ jsmntok_t *tokens, unsigned int num_tokens);
71
+
72
+ #ifdef __cplusplus
73
+ }
74
+ #endif
75
+
76
+ #endif /* __JSMN_H_ */
Binary file
@@ -1,5 +1,8 @@
1
1
  require 'prometheus/client/uses_value_type'
2
2
  require 'prometheus/client/helper/json_parser'
3
+ require 'prometheus/client/helper/plain_file'
4
+ require 'prometheus/client/helper/metrics_processing'
5
+ require 'prometheus/client/helper/metrics_representation'
3
6
 
4
7
  module Prometheus
5
8
  module Client
@@ -10,166 +13,68 @@ module Prometheus
10
13
  VERSION = '0.0.4'.freeze
11
14
  CONTENT_TYPE = "#{MEDIA_TYPE}; version=#{VERSION}".freeze
12
15
 
13
- METRIC_LINE = '%s%s %s'.freeze
14
- TYPE_LINE = '# TYPE %s %s'.freeze
15
- HELP_LINE = '# HELP %s %s'.freeze
16
-
17
- LABEL = '%s="%s"'.freeze
18
- SEPARATOR = ','.freeze
19
- DELIMITER = "\n".freeze
20
-
21
- REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }.freeze
22
- REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }.freeze
23
-
24
16
  class << self
25
17
  def marshal(registry)
26
- lines = []
27
18
 
28
- registry.metrics.each do |metric|
29
- lines << format(HELP_LINE, metric.name, escape(metric.docstring))
30
- lines << format(TYPE_LINE, metric.name, metric.type)
31
- metric.values.each do |label_set, value|
32
- representation(metric, label_set, value) { |l| lines << l }
19
+ metrics = registry.metrics.map do |metric|
20
+ samples = metric.values.flat_map do |label_set, value|
21
+ representation(metric, label_set, value)
33
22
  end
23
+
24
+ [metric.name, { type: metric.type, help: metric.docstring, samples: samples }]
34
25
  end
35
26
 
36
- # there must be a trailing delimiter
37
- (lines << nil).join(DELIMITER)
27
+ Helper::MetricsRepresentation.to_text(metrics)
38
28
  end
39
29
 
40
30
  def marshal_multiprocess(path = Prometheus::Client.configuration.multiprocess_files_dir)
41
31
  metrics = load_metrics(path)
42
32
 
43
- lines = []
44
- merge_metrics(metrics).each do |name, metric|
45
- lines << format(HELP_LINE, name, escape(metric[:help]))
46
- lines << format(TYPE_LINE, name, metric[:type])
47
-
48
- metric[:samples].each do |metric_name, labels, value|
49
- lines << metric(metric_name, format_labels(labels), value)
50
- end
51
- end
52
- (lines << nil).join(DELIMITER)
33
+ Helper::MetricsRepresentation.to_text(Helper::MetricsProcessing.merge_metrics(metrics))
53
34
  end
54
35
 
55
36
  private
56
37
 
57
- def merge_metrics(metrics)
58
- metrics.each_value do |metric|
59
- metric[:samples] = merge_samples(metric[:samples], metric[:type], metric[:multiprocess_mode]).map do |(name, labels), value|
60
- [name, labels.to_h, value]
61
- end
62
- end
63
- end
64
-
65
- def merge_samples(raw_samples, metric_type, multiprocess_mode)
66
- samples = {}
67
- raw_samples.each do |name, labels, value|
68
- without_pid = labels.reject { |l| l[0] == 'pid' }
69
-
70
- case metric_type
71
- when :gauge
72
- case multiprocess_mode
73
- when 'min'
74
- s = samples.fetch([name, without_pid], value)
75
- samples[[name, without_pid]] = [s, value].min
76
- when 'max'
77
- s = samples.fetch([name, without_pid], value)
78
- samples[[name, without_pid]] = [s, value].max
79
- when 'livesum'
80
- s = samples.fetch([name, without_pid], 0.0)
81
- samples[[name, without_pid]] = s + value
82
- else # all/liveall
83
- samples[[name, labels]] = value
84
- end
85
- else
86
- # Counter, Histogram and Summary.
87
- s = samples.fetch([name, without_pid], 0.0)
88
- samples[[name, without_pid]] = s + value
89
- end
90
- end
91
-
92
- samples
93
- end
94
-
95
38
  def load_metrics(path)
96
39
  metrics = {}
97
40
  Dir.glob(File.join(path, '*.db')).sort.each do |f|
98
- parts = File.basename(f, '.db').split('_')
99
- parts[-1].gsub!(/(?<=.)-\d+/, '') # remove trailing file number
100
- type = parts[0].to_sym
101
-
102
- MmapedDict.read_all_values(f).each do |key, value|
103
- metric_name, name, labelnames, labelvalues = Helper::JsonParser.load(key)
104
- metric = metrics.fetch(metric_name,
105
- metric_name: metric_name,
106
- help: 'Multiprocess metric',
107
- type: type,
108
- samples: []
109
- )
110
- if type == :gauge
111
- pid = parts[2..-1].join('_')
112
- metric[:multiprocess_mode] = parts[1]
113
- metric[:samples] += [[name, labelnames.zip(labelvalues) + [['pid', pid]], value]]
114
- else
115
- # The duplicates and labels are fixed in the next for.
116
- metric[:samples] += [[name, labelnames.zip(labelvalues), value]]
117
- end
118
- metrics[metric_name] = metric
119
- end
41
+ Helper::PlainFile.new(f).to_metrics(metrics)
120
42
  end
121
43
  metrics
122
44
  end
123
45
 
124
- def representation(metric, label_set, value, &block)
125
- set = metric.base_labels.merge(label_set)
46
+ def representation(metric, label_set, value)
47
+ labels = metric.base_labels.merge(label_set)
126
48
 
127
49
  if metric.type == :summary
128
- summary(metric.name, set, value, &block)
50
+ summary(metric.name, labels, value)
129
51
  elsif metric.type == :histogram
130
- histogram(metric.name, set, value, &block)
52
+ histogram(metric.name, labels, value)
131
53
  else
132
- yield metric(metric.name, format_labels(set), value.get)
54
+ [[metric.name, labels, value.get]]
133
55
  end
134
56
  end
135
57
 
136
58
  def summary(name, set, value)
137
- value.get.each do |q, v|
138
- yield metric(name, format_labels(set.merge(quantile: q)), v)
59
+ rv = value.get.map do |q, v|
60
+ [name, set.merge(quantile: q), v]
139
61
  end
140
62
 
141
- l = format_labels(set)
142
- yield metric("#{name}_sum", l, value.get.sum)
143
- yield metric("#{name}_count", l, value.get.total)
63
+ rv << ["#{name}_sum", set, value.get.sum]
64
+ rv << ["#{name}_count", set, value.get.total]
65
+ rv
144
66
  end
145
67
 
146
68
  def histogram(name, set, value)
147
- value.get.each do |q, v|
148
- yield metric(name, format_labels(set.merge(le: q)), v)
149
- end
150
- yield metric(name, format_labels(set.merge(le: '+Inf')), value.get.total)
151
-
152
- l = format_labels(set)
153
- yield metric("#{name}_sum", l, value.get.sum)
154
- yield metric("#{name}_count", l, value.get.total)
155
- end
156
-
157
- def metric(name, labels, value)
158
- format(METRIC_LINE, name, labels, value)
159
- end
160
-
161
- def format_labels(set)
162
- return if set.empty?
163
-
164
- strings = set.each_with_object([]) do |(key, value), memo|
165
- memo << format(LABEL, key, escape(value, :label))
69
+ # |metric_name, labels, value|
70
+ rv = value.get.map do |q, v|
71
+ [name, set.merge(le: q), v]
166
72
  end
167
73
 
168
- "{#{strings.join(SEPARATOR)}}"
169
- end
170
-
171
- def escape(string, format = :doc)
172
- string.to_s.gsub(REGEX[format], REPLACE)
74
+ rv << [name, set.merge(le: '+Inf'), value.get.total]
75
+ rv << ["#{name}_sum", set, value.get.sum]
76
+ rv << ["#{name}_count", set, value.get.total]
77
+ rv
173
78
  end
174
79
  end
175
80
  end
@@ -1,7 +1,12 @@
1
+ require 'prometheus/client/helper/json_parser'
2
+ require 'fast_mmaped_file'
3
+
1
4
  module Prometheus
2
5
  module Client
3
6
  module Helper
4
7
  module EntryParser
8
+ class ParsingError < RuntimeError;
9
+ end
5
10
  MINIMUM_SIZE = 8
6
11
  START_POSITION = 8
7
12
 
@@ -9,6 +14,24 @@ module Prometheus
9
14
  slice(0..3).unpack('l')[0]
10
15
  end
11
16
 
17
+ def parts
18
+ @parts ||= File.basename(filepath, '.db')
19
+ .split('_')
20
+ .map { |e| e.gsub(/-\d+$/, '') } # remove trailing -number
21
+ end
22
+
23
+ def type
24
+ parts[0].to_sym
25
+ end
26
+
27
+ def pid
28
+ parts[2..-1].join('_')
29
+ end
30
+
31
+ def multiprocess_mode
32
+ parts[1]
33
+ end
34
+
12
35
  def empty?
13
36
  size < MINIMUM_SIZE || used.zero?
14
37
  end
@@ -38,6 +61,101 @@ module Prometheus
38
61
  end
39
62
  end
40
63
  end
64
+
65
+ def parsed_entries(ignore_errors = false)
66
+ result = entries.map do |data, encoded_len, value_offset, _|
67
+ begin
68
+ encoded, value = data.unpack(format('@4A%d@%dd', encoded_len, value_offset))
69
+ [encoded, value]
70
+ rescue ArgumentError => e
71
+ Prometheus::Client.logger.debug("Error processing data: #{bin_to_hex(data[0, 7])} len: #{encoded_len} value_offset: #{value_offset}")
72
+ raise ParsingError(e) unless ignore_errors
73
+ end
74
+ end
75
+
76
+ result.reject { |e| e.nil? } if ignore_errors
77
+ result
78
+ end
79
+
80
+ def to_metrics(metrics = {}, ignore_errors = false)
81
+ FastMmapedFile.fast_entries(data) do |metric_name, name, labels, value|
82
+ begin
83
+ metric = metrics.fetch(metric_name,
84
+ metric_name: metric_name,
85
+ help: 'Multiprocess metric',
86
+ type: type,
87
+ samples: {}
88
+ )
89
+ if type == :gauge
90
+ metric[:multiprocess_mode] = multiprocess_mode
91
+ merge_samples(name, labels, value, metric[:samples])
92
+ else
93
+ merge_samples(name, labels, value, metric[:samples])
94
+ # The duplicates and labels are fixed in the next for.
95
+ end
96
+ metrics[metric_name] = metric
97
+ rescue JSON::ParserError => e
98
+ raise ParsingError(e) unless ignore_errors
99
+ end
100
+ end
101
+
102
+ metrics.reject { |e| e.nil? } if ignore_errors
103
+ metrics
104
+ end
105
+
106
+
107
+ def min(a, b)
108
+ if a < b
109
+ a
110
+ else
111
+ b
112
+ end
113
+ end
114
+
115
+ def max(a, b)
116
+ if a > b
117
+ a
118
+ else
119
+ b
120
+ end
121
+ end
122
+
123
+ def merge_samples(name, labels, value, samples)
124
+ samples ||= {}
125
+ case type
126
+ when :gauge
127
+ case multiprocess_mode
128
+ when 'min'
129
+ key = labels.merge!(__key_name: name)
130
+ s = samples.fetch(key, value)
131
+ samples[key] = min(s, value)
132
+ when 'max'
133
+ key = labels.merge!(__key_name: name)
134
+ s = samples.fetch(key, value)
135
+ samples[key] = max(s, value)
136
+ when 'livesum'
137
+ key = labels.merge!(__key_name: name)
138
+ s = samples.fetch(key, 0.0)
139
+ samples[key] = s + value
140
+ else # all/liveall
141
+ key = labels.merge!(pid: pid, __key_name: name)
142
+ samples[key] = value
143
+ end
144
+ else
145
+ key = labels.merge!(__key_name: name)
146
+ # Counter, Histogram and Summary.
147
+ s = samples.fetch(key, 0.0)
148
+ samples[key] = s + value
149
+ end
150
+
151
+ samples
152
+ end
153
+
154
+ private
155
+
156
+ def bin_to_hex(s)
157
+ s.each_byte.map { |b| b.to_s(16) }.join
158
+ end
41
159
  end
42
160
  end
43
161
  end
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+
1
3
  module Prometheus
2
4
  module Client
3
5
  module Helper
@@ -0,0 +1,62 @@
1
+ module Prometheus
2
+ module Client
3
+ module Helper
4
+ module MetricsProcessing
5
+ def self.merge_metrics(metrics)
6
+ metrics
7
+ end
8
+
9
+ def self.min(a, b)
10
+ if a < b
11
+ a
12
+ else
13
+ b
14
+ end
15
+ end
16
+
17
+ def self.max(a, b)
18
+ if a > b
19
+ a
20
+ else
21
+ b
22
+ end
23
+ end
24
+
25
+ def self.merge_samples(raw_samples, metric_type, multiprocess_mode)
26
+ samples = {}
27
+ raw_samples.each do |name, labels, value|
28
+ key = { name: name, labels: labels }
29
+ # without_pid = labels.delete(:pid)
30
+
31
+ case metric_type
32
+ when :gauge
33
+ case multiprocess_mode
34
+ when 'min'
35
+ labels.delete(:pid)
36
+ s = samples.fetch(key, value)
37
+ samples[key] = min(s, value)
38
+ when 'max'
39
+ labels.delete(:pid)
40
+ s = samples.fetch(key, value)
41
+ samples[key] = max(s, value)
42
+ when 'livesum'
43
+ labels.delete(:pid)
44
+ s = samples.fetch(key, 0.0)
45
+ samples[key] = s + value
46
+ else # all/liveall
47
+ samples[key] = value
48
+ end
49
+ else
50
+ labels.delete(:pid)
51
+ # Counter, Histogram and Summary.
52
+ s = samples.fetch(key, 0.0)
53
+ samples[key] = s + value
54
+ end
55
+ end
56
+
57
+ samples
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,52 @@
1
+ module Prometheus
2
+ module Client
3
+ module Helper
4
+ module MetricsRepresentation
5
+ METRIC_LINE = '%s%s %s'.freeze
6
+ TYPE_LINE = '# TYPE %s %s'.freeze
7
+ HELP_LINE = '# HELP %s %s'.freeze
8
+
9
+ LABEL = '%s="%s"'.freeze
10
+ SEPARATOR = ','.freeze
11
+ DELIMITER = "\n".freeze
12
+
13
+ REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }.freeze
14
+ REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }.freeze
15
+
16
+ def self.to_text(metrics)
17
+ lines = []
18
+
19
+ metrics.each do |name, metric|
20
+ lines << format(HELP_LINE, name, escape(metric[:help]))
21
+ lines << format(TYPE_LINE, name, metric[:type])
22
+ metric[:samples].each do |labels, value|
23
+ key = labels.delete(:__key_name)
24
+ lines << metric(key, format_labels(labels), value)
25
+ end
26
+ end
27
+
28
+ # there must be a trailing delimiter
29
+ (lines << nil).join(DELIMITER)
30
+ end
31
+
32
+ def self.metric(name, labels, value)
33
+ format(METRIC_LINE, name, labels, value)
34
+ end
35
+
36
+ def self.format_labels(set) #TODO: rename set
37
+ return if set.empty?
38
+
39
+ strings = set.each_with_object([]) do |(key, value), memo|
40
+ memo << format(LABEL, key, escape(value, :label))
41
+ end
42
+
43
+ "{#{strings.join(SEPARATOR)}}"
44
+ end
45
+
46
+ def self.escape(string, format = :doc)
47
+ string.to_s.gsub(REGEX[format], REPLACE)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -5,8 +5,10 @@ module Prometheus
5
5
  module Helper
6
6
  class PlainFile
7
7
  include EntryParser
8
+ attr_reader :filepath, :data
8
9
 
9
10
  def initialize(filepath)
11
+ @filepath = filepath
10
12
  @data = File.read(filepath, mode: 'rb')
11
13
  end
12
14
 
@@ -1,5 +1,5 @@
1
1
  module Prometheus
2
2
  module Client
3
- VERSION = '0.7.0.beta30'.freeze
3
+ VERSION = '0.7.0.beta31'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prometheus-client-mmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0.beta30
4
+ version: 0.7.0.beta31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Schmidt
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-11-17 00:00:00.000000000 Z
12
+ date: 2017-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mmap2
@@ -71,6 +71,8 @@ files:
71
71
  - README.md
72
72
  - ext/fast_mmaped_file/extconf.rb
73
73
  - ext/fast_mmaped_file/fast_mmaped_file.c
74
+ - ext/fast_mmaped_file/jsmn.c
75
+ - ext/fast_mmaped_file/jsmn.h
74
76
  - ext/fast_mmaped_file/mmap.h
75
77
  - lib/fast_mmaped_file.bundle
76
78
  - lib/prometheus.rb
@@ -82,6 +84,8 @@ files:
82
84
  - lib/prometheus/client/helper/entry_parser.rb
83
85
  - lib/prometheus/client/helper/file_locker.rb
84
86
  - lib/prometheus/client/helper/json_parser.rb
87
+ - lib/prometheus/client/helper/metrics_processing.rb
88
+ - lib/prometheus/client/helper/metrics_representation.rb
85
89
  - lib/prometheus/client/helper/mmaped_file.rb
86
90
  - lib/prometheus/client/helper/plain_file.rb
87
91
  - lib/prometheus/client/histogram.rb
@@ -118,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
122
  version: 1.3.1
119
123
  requirements: []
120
124
  rubyforge_project:
121
- rubygems_version: 2.6.8
125
+ rubygems_version: 2.6.14
122
126
  signing_key:
123
127
  specification_version: 4
124
128
  summary: A suite of instrumentation metric primitivesthat can be exposed through a