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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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