prometheus-client-mmap 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,108 @@
1
+ require 'prometheus/client/helper/mmaped_file'
2
+ require 'prometheus/client'
3
+
4
+ module Prometheus
5
+ module Client
6
+ class ParsingError < StandardError
7
+ end
8
+
9
+ # A dict of doubles, backed by an mmapped file.
10
+ #
11
+ # The file starts with a 4 byte int, indicating how much of it is used.
12
+ # Then 4 bytes of padding.
13
+ # There's then a number of entries, consisting of a 4 byte int which is the
14
+ # size of the next field, a utf-8 encoded string key, padding to an 8 byte
15
+ # alignment, and then a 8 byte float which is the value.
16
+ #
17
+ # For example, for :
18
+ #
19
+ # 0: | PID (4 bytes) | Used bytes (4 bytes) |
20
+ # 1: | Padding (4 bytes) | Size of field (4 bytes) |
21
+ # 2:
22
+ # 2: | Entry 1 key (N bytes) | Padding (4 bytes) |
23
+ # 3: | Entry 1 value (8 bytes) |
24
+ # 4: | Size of field 2 | Padding (4 bytes) |
25
+ # 5: | Entry 2 key (4 bytes) | Padding (4 byte) |
26
+ # 6: | Entry 2 value (8 bytes) |
27
+ class MmapedDict
28
+ MINIMUM_SIZE = 8
29
+ attr_reader :m, :used, :positions
30
+
31
+ def initialize(m)
32
+ @mutex = Mutex.new
33
+
34
+ @m = m
35
+ # @m.mlock # TODO: Ensure memory is locked to RAM
36
+
37
+ @used = @m.used
38
+
39
+ @positions = {}
40
+ read_all_positions.each do |key, pos|
41
+ @positions[key] = pos
42
+ end
43
+ rescue StandardError => e
44
+ raise ParsingError, "exception #{e} while processing metrics file #{path}"
45
+ end
46
+
47
+ # Yield (key, value). No locking is performed.
48
+ def self.read_all_values(f)
49
+ m = Helper::MmapedFile.open(f)
50
+
51
+ m.entries.map do |data, encoded_len, value_offset, _|
52
+ encoded, value = data.unpack(format('@4A%d@%dd', encoded_len, value_offset))
53
+ [encoded, value]
54
+ end
55
+ ensure
56
+ m.munmap
57
+ end
58
+
59
+ def read_value(key)
60
+ @mutex.synchronize do
61
+ init_value(key) unless @positions.key?(key)
62
+ end
63
+ pos = @positions[key]
64
+ # We assume that reading from an 8 byte aligned value is atomic.
65
+ @m[pos..pos + 7].unpack('d')[0]
66
+ end
67
+
68
+ def write_value(key, value)
69
+ @mutex.synchronize do
70
+ init_value(key) unless @positions.key?(key)
71
+ end
72
+ pos = @positions[key]
73
+ # We assume that writing to an 8 byte aligned value is atomic.
74
+ @m[pos..pos + 7] = [value].pack('d')
75
+ end
76
+
77
+ def path
78
+ @m.filepath unless @m.nil?
79
+ end
80
+
81
+ def close
82
+ @m.close
83
+ rescue TypeError => e
84
+ Prometheus::Client.logger.warn("munmap raised error #{e}")
85
+ end
86
+
87
+ private
88
+
89
+ # Initialize a value. Lock must be held by caller.
90
+ def init_value(key)
91
+ @m.add_entry(key, 0.0)
92
+
93
+ # Update how much space we've used.
94
+ @used = @m.used
95
+
96
+ @positions[key] = @used - 8
97
+ end
98
+
99
+ # Yield (key, pos). No locking is performed.
100
+ def read_all_positions
101
+ @m.entries.map do |data, encoded_len, _, absolute_pos|
102
+ encoded, = data.unpack(format('@4A%d', encoded_len))
103
+ [encoded, absolute_pos]
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,132 @@
1
+ require 'prometheus/client/helper/json_parser'
2
+
3
+ module Prometheus
4
+ module Client
5
+ module Helper
6
+ module EntryParser
7
+ class ParsingError < RuntimeError;
8
+ end
9
+
10
+ MINIMUM_SIZE = 8
11
+ START_POSITION = 8
12
+ VALUE_BYTES = 8
13
+ ENCODED_LENGTH_BYTES = 4
14
+
15
+ def used
16
+ slice(0..3).unpack('l')[0]
17
+ end
18
+
19
+ def parts
20
+ @parts ||= File.basename(filepath, '.db')
21
+ .split('_')
22
+ .map { |e| e.gsub(/-\d+$/, '') } # remove trailing -number
23
+ end
24
+
25
+ def type
26
+ parts[0].to_sym
27
+ end
28
+
29
+ def pid
30
+ parts[2..-1].join('_')
31
+ end
32
+
33
+ def multiprocess_mode
34
+ parts[1]
35
+ end
36
+
37
+ def empty?
38
+ size < MINIMUM_SIZE || used.zero?
39
+ end
40
+
41
+ def entries(ignore_errors = false)
42
+ return Enumerator.new {} if empty?
43
+
44
+ Enumerator.new do |yielder|
45
+ used_ = used # cache used to avoid unnecessary unpack operations
46
+
47
+ pos = START_POSITION # used + padding offset
48
+ while pos < used_ && pos < size && pos > 0
49
+ data = slice(pos..-1)
50
+ unless data
51
+ raise ParsingError, "data slice is nil at pos #{pos}" unless ignore_errors
52
+ pos += 8c
53
+ next
54
+ end
55
+
56
+ encoded_len, first_encoded_bytes = data.unpack('LL')
57
+ if encoded_len.nil? || encoded_len.zero? || first_encoded_bytes.nil? || first_encoded_bytes.zero?
58
+ # do not parse empty data
59
+ pos += 8
60
+ next
61
+ end
62
+
63
+ entry_len = ENCODED_LENGTH_BYTES + encoded_len
64
+ padding_len = 8 - entry_len % 8
65
+
66
+ value_offset = entry_len + padding_len # align to 8 bytes
67
+ pos += value_offset
68
+
69
+ if value_offset > 0 && (pos + VALUE_BYTES) < size # if positions are safe
70
+ yielder.yield data, encoded_len, value_offset, pos
71
+ else
72
+ raise ParsingError, "data slice is nil at pos #{pos}" unless ignore_errors
73
+ end
74
+ pos += VALUE_BYTES
75
+ end
76
+ end
77
+ end
78
+
79
+ def parsed_entries(ignore_errors = false)
80
+ result = entries(ignore_errors).map do |data, encoded_len, value_offset, _|
81
+ begin
82
+ encoded, value = data.unpack(format('@4A%d@%dd', encoded_len, value_offset))
83
+ [encoded, value]
84
+ rescue ArgumentError => e
85
+ Prometheus::Client.logger.debug("Error processing data: #{bin_to_hex(data[0, 7])} len: #{encoded_len} value_offset: #{value_offset}")
86
+ raise ParsingError, e unless ignore_errors
87
+ end
88
+ end
89
+
90
+ metrics.reject!(&:nil?) if ignore_errors
91
+ result
92
+ end
93
+
94
+ def to_metrics(metrics = {}, ignore_errors = false)
95
+ parsed_entries(ignore_errors).each do |key, value|
96
+ begin
97
+ metric_name, name, labelnames, labelvalues = JsonParser.load(key)
98
+ labelnames ||= []
99
+ labelvalues ||= []
100
+
101
+ metric = metrics.fetch(metric_name,
102
+ metric_name: metric_name,
103
+ help: 'Multiprocess metric',
104
+ type: type,
105
+ samples: [])
106
+ if type == :gauge
107
+ metric[:multiprocess_mode] = multiprocess_mode
108
+ metric[:samples] += [[name, labelnames.zip(labelvalues) + [['pid', pid]], value]]
109
+ else
110
+ # The duplicates and labels are fixed in the next for.
111
+ metric[:samples] += [[name, labelnames.zip(labelvalues), value]]
112
+ end
113
+ metrics[metric_name] = metric
114
+
115
+ rescue JSON::ParserError => e
116
+ raise ParsingError(e) unless ignore_errors
117
+ end
118
+ end
119
+
120
+ metrics.reject!(&:nil?) if ignore_errors
121
+ metrics
122
+ end
123
+
124
+ private
125
+
126
+ def bin_to_hex(s)
127
+ s.each_byte.map { |b| b.to_s(16) }.join
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,5 +1,5 @@
1
1
  module Prometheus
2
2
  module Client
3
- VERSION = '0.9.1'.freeze
3
+ VERSION = '0.9.2'.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.9.1
4
+ version: 0.9.2
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: 2018-02-07 00:00:00.000000000 Z
12
+ date: 2018-05-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fuzzbert
@@ -83,6 +83,8 @@ extensions:
83
83
  extra_rdoc_files: []
84
84
  files:
85
85
  - README.md
86
+ - ext/fast_mmaped_file/#file_parsing.c#
87
+ - ext/fast_mmaped_file/#value_access.c#
86
88
  - ext/fast_mmaped_file/extconf.rb
87
89
  - ext/fast_mmaped_file/fast_mmaped_file.c
88
90
  - ext/fast_mmaped_file/file_format.c
@@ -92,8 +94,6 @@ files:
92
94
  - ext/fast_mmaped_file/file_reading.c
93
95
  - ext/fast_mmaped_file/file_reading.h
94
96
  - ext/fast_mmaped_file/globals.h
95
- - ext/fast_mmaped_file/hashmap.c
96
- - ext/fast_mmaped_file/jsmn.c
97
97
  - ext/fast_mmaped_file/mmap.c
98
98
  - ext/fast_mmaped_file/mmap.h
99
99
  - ext/fast_mmaped_file/rendering.c
@@ -102,13 +102,15 @@ files:
102
102
  - ext/fast_mmaped_file/utils.h
103
103
  - ext/fast_mmaped_file/value_access.c
104
104
  - ext/fast_mmaped_file/value_access.h
105
- - lib/fast_mmaped_file.bundle
106
105
  - lib/prometheus.rb
107
106
  - lib/prometheus/client.rb
107
+ - lib/prometheus/client/#histogram.rb#
108
+ - lib/prometheus/client/#mmaped_dict.rb#
108
109
  - lib/prometheus/client/configuration.rb
109
110
  - lib/prometheus/client/counter.rb
110
111
  - lib/prometheus/client/formats/text.rb
111
112
  - lib/prometheus/client/gauge.rb
113
+ - lib/prometheus/client/helper/#entry_parser.rb#
112
114
  - lib/prometheus/client/helper/entry_parser.rb
113
115
  - lib/prometheus/client/helper/file_locker.rb
114
116
  - lib/prometheus/client/helper/json_parser.rb
@@ -144,7 +146,6 @@ files:
144
146
  - vendor/c/jsmn/example/simple.c
145
147
  - vendor/c/jsmn/jsmn.c
146
148
  - vendor/c/jsmn/jsmn.h
147
- - vendor/c/jsmn/jsmn.o
148
149
  - vendor/c/jsmn/library.json
149
150
  - vendor/c/jsmn/test/test.h
150
151
  - vendor/c/jsmn/test/tests.c
@@ -169,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
170
  version: '0'
170
171
  requirements: []
171
172
  rubyforge_project:
172
- rubygems_version: 2.6.8
173
+ rubygems_version: 2.5.2.2
173
174
  signing_key:
174
175
  specification_version: 4
175
176
  summary: A suite of instrumentation metric primitivesthat can be exposed through a
@@ -1,692 +0,0 @@
1
- /*
2
- * Copyright (c) 2016-2017 David Leeds <davidesleeds@gmail.com>
3
- *
4
- * Hashmap is free software; you can redistribute it and/or modify
5
- * it under the terms of the MIT license. See LICENSE for details.
6
- */
7
-
8
- #include <stdlib.h>
9
- #include <stdint.h>
10
- #include <stdbool.h>
11
- #include <string.h>
12
- #include <errno.h>
13
-
14
- #include "hashmap.h"
15
-
16
- #ifndef HASHMAP_NOASSERT
17
- #include <assert.h>
18
- #define HASHMAP_ASSERT(expr) assert(expr)
19
- #else
20
- #define HASHMAP_ASSERT(expr)
21
- #endif
22
-
23
- /* Table sizes must be powers of 2 */
24
- #define HASHMAP_SIZE_MIN (1 << 5) /* 32 */
25
- #define HASHMAP_SIZE_DEFAULT (1 << 8) /* 256 */
26
- #define HASHMAP_SIZE_MOD(map, val) ((val) & ((map)->table_size - 1))
27
-
28
- /* Limit for probing is 1/2 of table_size */
29
- #define HASHMAP_PROBE_LEN(map) ((map)->table_size >> 1)
30
- /* Return the next linear probe index */
31
- #define HASHMAP_PROBE_NEXT(map, index) HASHMAP_SIZE_MOD(map, (index) + 1)
32
-
33
- /* Check if index b is less than or equal to index a */
34
- #define HASHMAP_INDEX_LE(map, a, b) \
35
- ((a) == (b) || (((b) - (a)) & ((map)->table_size >> 1)) != 0)
36
-
37
-
38
- struct hashmap_entry {
39
- void *key;
40
- void *data;
41
- #ifdef HASHMAP_METRICS
42
- size_t num_collisions;
43
- #endif
44
- };
45
-
46
-
47
- /*
48
- * Enforce a maximum 0.75 load factor.
49
- */
50
- static inline size_t hashmap_table_min_size_calc(size_t num_entries)
51
- {
52
- return num_entries + (num_entries / 3);
53
- }
54
-
55
- /*
56
- * Calculate the optimal table size, given the specified max number
57
- * of elements.
58
- */
59
- static size_t hashmap_table_size_calc(size_t num_entries)
60
- {
61
- size_t table_size;
62
- size_t min_size;
63
-
64
- table_size = hashmap_table_min_size_calc(num_entries);
65
-
66
- /* Table size is always a power of 2 */
67
- min_size = HASHMAP_SIZE_MIN;
68
- while (min_size < table_size) {
69
- min_size <<= 1;
70
- }
71
- return min_size;
72
- }
73
-
74
- /*
75
- * Get a valid hash table index from a key.
76
- */
77
- static inline size_t hashmap_calc_index(const struct hashmap *map,
78
- const void *key)
79
- {
80
- return HASHMAP_SIZE_MOD(map, map->hash(key));
81
- }
82
-
83
- /*
84
- * Return the next populated entry, starting with the specified one.
85
- * Returns NULL if there are no more valid entries.
86
- */
87
- static struct hashmap_entry *hashmap_entry_get_populated(
88
- const struct hashmap *map, struct hashmap_entry *entry)
89
- {
90
- for (; entry < &map->table[map->table_size]; ++entry) {
91
- if (entry->key) {
92
- return entry;
93
- }
94
- }
95
- return NULL;
96
- }
97
-
98
- /*
99
- * Find the hashmap entry with the specified key, or an empty slot.
100
- * Returns NULL if the entire table has been searched without finding a match.
101
- */
102
- static struct hashmap_entry *hashmap_entry_find(const struct hashmap *map,
103
- const void *key, bool find_empty)
104
- {
105
- size_t i;
106
- size_t index;
107
- size_t probe_len = HASHMAP_PROBE_LEN(map);
108
- struct hashmap_entry *entry;
109
-
110
- index = hashmap_calc_index(map, key);
111
-
112
- /* Linear probing */
113
- for (i = 0; i < probe_len; ++i) {
114
- entry = &map->table[index];
115
- if (!entry->key) {
116
- if (find_empty) {
117
- #ifdef HASHMAP_METRICS
118
- entry->num_collisions = i;
119
- #endif
120
- return entry;
121
- }
122
- return NULL;
123
- }
124
- if (map->key_compare(key, entry->key) == 0) {
125
- return entry;
126
- }
127
- index = HASHMAP_PROBE_NEXT(map, index);
128
- }
129
- return NULL;
130
- }
131
-
132
- /*
133
- * Removes the specified entry and processes the proceeding entries to reduce
134
- * the load factor and keep the chain continuous. This is a required
135
- * step for hash maps using linear probing.
136
- */
137
- static void hashmap_entry_remove(struct hashmap *map,
138
- struct hashmap_entry *removed_entry)
139
- {
140
- size_t i;
141
- #ifdef HASHMAP_METRICS
142
- size_t removed_i = 0;
143
- #endif
144
- size_t index;
145
- size_t entry_index;
146
- size_t removed_index = (removed_entry - map->table);
147
- struct hashmap_entry *entry;
148
-
149
- /* Free the key */
150
- if (map->key_free) {
151
- map->key_free(removed_entry->key);
152
- }
153
- --map->num_entries;
154
-
155
- /* Fill the free slot in the chain */
156
- index = HASHMAP_PROBE_NEXT(map, removed_index);
157
- for (i = 1; i < map->table_size; ++i) {
158
- entry = &map->table[index];
159
- if (!entry->key) {
160
- /* Reached end of chain */
161
- break;
162
- }
163
- entry_index = hashmap_calc_index(map, entry->key);
164
- /* Shift in entries with an index <= to the removed slot */
165
- if (HASHMAP_INDEX_LE(map, removed_index, entry_index)) {
166
- #ifdef HASHMAP_METRICS
167
- entry->num_collisions -= (i - removed_i);
168
- removed_i = i;
169
- #endif
170
- memcpy(removed_entry, entry, sizeof(*removed_entry));
171
- removed_index = index;
172
- removed_entry = entry;
173
- }
174
- index = HASHMAP_PROBE_NEXT(map, index);
175
- }
176
- /* Clear the last removed entry */
177
- memset(removed_entry, 0, sizeof(*removed_entry));
178
- }
179
-
180
- /*
181
- * Reallocates the hash table to the new size and rehashes all entries.
182
- * new_size MUST be a power of 2.
183
- * Returns 0 on success and -errno on allocation or hash function failure.
184
- */
185
- static int hashmap_rehash(struct hashmap *map, size_t new_size)
186
- {
187
- size_t old_size;
188
- struct hashmap_entry *old_table;
189
- struct hashmap_entry *new_table;
190
- struct hashmap_entry *entry;
191
- struct hashmap_entry *new_entry;
192
-
193
- HASHMAP_ASSERT(new_size >= HASHMAP_SIZE_MIN);
194
- HASHMAP_ASSERT((new_size & (new_size - 1)) == 0);
195
-
196
- new_table = (struct hashmap_entry *)calloc(new_size,
197
- sizeof(struct hashmap_entry));
198
- if (!new_table) {
199
- return -ENOMEM;
200
- }
201
- /* Backup old elements in case of rehash failure */
202
- old_size = map->table_size;
203
- old_table = map->table;
204
- map->table_size = new_size;
205
- map->table = new_table;
206
- /* Rehash */
207
- for (entry = old_table; entry < &old_table[old_size]; ++entry) {
208
- if (!entry->data) {
209
- /* Only copy entries with data */
210
- continue;
211
- }
212
- new_entry = hashmap_entry_find(map, entry->key, true);
213
- if (!new_entry) {
214
- /*
215
- * The load factor is too high with the new table
216
- * size, or a poor hash function was used.
217
- */
218
- goto revert;
219
- }
220
- /* Shallow copy (intentionally omits num_collisions) */
221
- new_entry->key = entry->key;
222
- new_entry->data = entry->data;
223
- }
224
- free(old_table);
225
- return 0;
226
- revert:
227
- map->table_size = old_size;
228
- map->table = old_table;
229
- free(new_table);
230
- return -EINVAL;
231
- }
232
-
233
- /*
234
- * Iterate through all entries and free all keys.
235
- */
236
- static void hashmap_free_keys(struct hashmap *map)
237
- {
238
- struct hashmap_iter *iter;
239
-
240
- if (!map->key_free) {
241
- return;
242
- }
243
- for (iter = hashmap_iter(map); iter;
244
- iter = hashmap_iter_next(map, iter)) {
245
- map->key_free((void *)hashmap_iter_get_key(iter));
246
- }
247
- }
248
-
249
- /*
250
- * Initialize an empty hashmap. A hash function and a key comparator are
251
- * required.
252
- *
253
- * hash_func should return an even distribution of numbers between 0
254
- * and SIZE_MAX varying on the key provided.
255
- *
256
- * key_compare_func should return 0 if the keys match, and non-zero otherwise.
257
- *
258
- * initial_size is optional, and may be set to the max number of entries
259
- * expected to be put in the hash table. This is used as a hint to
260
- * pre-allocate the hash table to the minimum size needed to avoid
261
- * gratuitous rehashes. If initial_size 0, a default size will be used.
262
- *
263
- * Returns 0 on success and -errno on failure.
264
- */
265
- int hashmap_init(struct hashmap *map, size_t (*hash_func)(const void *),
266
- int (*key_compare_func)(const void *, const void *),
267
- size_t initial_size)
268
- {
269
- HASHMAP_ASSERT(map != NULL);
270
- HASHMAP_ASSERT(hash_func != NULL);
271
- HASHMAP_ASSERT(key_compare_func != NULL);
272
-
273
- if (!initial_size) {
274
- initial_size = HASHMAP_SIZE_DEFAULT;
275
- } else {
276
- /* Convert init size to valid table size */
277
- initial_size = hashmap_table_size_calc(initial_size);
278
- }
279
- map->table_size_init = initial_size;
280
- map->table_size = initial_size;
281
- map->num_entries = 0;
282
- map->table = (struct hashmap_entry *)calloc(initial_size,
283
- sizeof(struct hashmap_entry));
284
- if (!map->table) {
285
- return -ENOMEM;
286
- }
287
- map->hash = hash_func;
288
- map->key_compare = key_compare_func;
289
- map->key_alloc = NULL;
290
- map->key_free = NULL;
291
- return 0;
292
- }
293
-
294
- /*
295
- * Free the hashmap and all associated memory.
296
- */
297
- void hashmap_destroy(struct hashmap *map)
298
- {
299
- if (!map) {
300
- return;
301
- }
302
- hashmap_free_keys(map);
303
- free(map->table);
304
- memset(map, 0, sizeof(*map));
305
- }
306
-
307
- /*
308
- * Enable internal memory management of hash keys.
309
- */
310
- void hashmap_set_key_alloc_funcs(struct hashmap *map,
311
- void *(*key_alloc_func)(const void *),
312
- void (*key_free_func)(void *))
313
- {
314
- HASHMAP_ASSERT(map != NULL);
315
-
316
- map->key_alloc = key_alloc_func;
317
- map->key_free = key_free_func;
318
- }
319
-
320
- /*
321
- * Add an entry to the hashmap. If an entry with a matching key already
322
- * exists and has a data pointer associated with it, the existing data
323
- * pointer is returned, instead of assigning the new value. Compare
324
- * the return value with the data passed in to determine if a new entry was
325
- * created. Returns NULL if memory allocation failed.
326
- */
327
- void *hashmap_put(struct hashmap *map, const void *key, void *data)
328
- {
329
- struct hashmap_entry *entry;
330
-
331
- HASHMAP_ASSERT(map != NULL);
332
- HASHMAP_ASSERT(key != NULL);
333
-
334
- /* Rehash with 2x capacity if load factor is approaching 0.75 */
335
- if (map->table_size <= hashmap_table_min_size_calc(map->num_entries)) {
336
- hashmap_rehash(map, map->table_size << 1);
337
- }
338
- entry = hashmap_entry_find(map, key, true);
339
- if (!entry) {
340
- /*
341
- * Cannot find an empty slot. Either out of memory, or using
342
- * a poor hash function. Attempt to rehash once to reduce
343
- * chain length.
344
- */
345
- if (hashmap_rehash(map, map->table_size << 1) < 0) {
346
- return NULL;
347
- }
348
- entry = hashmap_entry_find(map, key, true);
349
- if (!entry) {
350
- return NULL;
351
- }
352
- }
353
- if (!entry->key) {
354
- /* Allocate copy of key to simplify memory management */
355
- if (map->key_alloc) {
356
- entry->key = map->key_alloc(key);
357
- if (!entry->key) {
358
- return NULL;
359
- }
360
- } else {
361
- entry->key = (void *)key;
362
- }
363
- ++map->num_entries;
364
- } else if (entry->data) {
365
- /* Do not overwrite existing data */
366
- return entry->data;
367
- }
368
- entry->data = data;
369
- return data;
370
- }
371
-
372
- /*
373
- * Return the data pointer, or NULL if no entry exists.
374
- */
375
- void *hashmap_get(const struct hashmap *map, const void *key)
376
- {
377
- struct hashmap_entry *entry;
378
-
379
- HASHMAP_ASSERT(map != NULL);
380
- HASHMAP_ASSERT(key != NULL);
381
-
382
- entry = hashmap_entry_find(map, key, false);
383
- if (!entry) {
384
- return NULL;
385
- }
386
- return entry->data;
387
- }
388
-
389
- /*
390
- * Remove an entry with the specified key from the map.
391
- * Returns the data pointer, or NULL, if no entry was found.
392
- */
393
- void *hashmap_remove(struct hashmap *map, const void *key)
394
- {
395
- struct hashmap_entry *entry;
396
- void *data;
397
-
398
- HASHMAP_ASSERT(map != NULL);
399
- HASHMAP_ASSERT(key != NULL);
400
-
401
- entry = hashmap_entry_find(map, key, false);
402
- if (!entry) {
403
- return NULL;
404
- }
405
- data = entry->data;
406
- /* Clear the entry and make the chain contiguous */
407
- hashmap_entry_remove(map, entry);
408
- return data;
409
- }
410
-
411
- /*
412
- * Remove all entries.
413
- */
414
- void hashmap_clear(struct hashmap *map)
415
- {
416
- HASHMAP_ASSERT(map != NULL);
417
-
418
- hashmap_free_keys(map);
419
- map->num_entries = 0;
420
- memset(map->table, 0, sizeof(struct hashmap_entry) * map->table_size);
421
- }
422
-
423
- /*
424
- * Remove all entries and reset the hash table to its initial size.
425
- */
426
- void hashmap_reset(struct hashmap *map)
427
- {
428
- struct hashmap_entry *new_table;
429
-
430
- HASHMAP_ASSERT(map != NULL);
431
-
432
- hashmap_clear(map);
433
- if (map->table_size == map->table_size_init) {
434
- return;
435
- }
436
- new_table = (struct hashmap_entry *)realloc(map->table,
437
- sizeof(struct hashmap_entry) * map->table_size_init);
438
- if (!new_table) {
439
- return;
440
- }
441
- map->table = new_table;
442
- map->table_size = map->table_size_init;
443
- }
444
-
445
- /*
446
- * Return the number of entries in the hash map.
447
- */
448
- size_t hashmap_size(const struct hashmap *map)
449
- {
450
- HASHMAP_ASSERT(map != NULL);
451
-
452
- return map->num_entries;
453
- }
454
-
455
- /*
456
- * Get a new hashmap iterator. The iterator is an opaque
457
- * pointer that may be used with hashmap_iter_*() functions.
458
- * Hashmap iterators are INVALID after a put or remove operation is performed.
459
- * hashmap_iter_remove() allows safe removal during iteration.
460
- */
461
- struct hashmap_iter *hashmap_iter(const struct hashmap *map)
462
- {
463
- HASHMAP_ASSERT(map != NULL);
464
-
465
- if (!map->num_entries) {
466
- return NULL;
467
- }
468
- return (struct hashmap_iter *)hashmap_entry_get_populated(map,
469
- map->table);
470
- }
471
-
472
- /*
473
- * Return an iterator to the next hashmap entry. Returns NULL if there are
474
- * no more entries.
475
- */
476
- struct hashmap_iter *hashmap_iter_next(const struct hashmap *map,
477
- const struct hashmap_iter *iter)
478
- {
479
- struct hashmap_entry *entry = (struct hashmap_entry *)iter;
480
-
481
- HASHMAP_ASSERT(map != NULL);
482
-
483
- if (!iter) {
484
- return NULL;
485
- }
486
- return (struct hashmap_iter *)hashmap_entry_get_populated(map,
487
- entry + 1);
488
- }
489
-
490
- /*
491
- * Remove the hashmap entry pointed to by this iterator and return an
492
- * iterator to the next entry. Returns NULL if there are no more entries.
493
- */
494
- struct hashmap_iter *hashmap_iter_remove(struct hashmap *map,
495
- const struct hashmap_iter *iter)
496
- {
497
- struct hashmap_entry *entry = (struct hashmap_entry *)iter;
498
-
499
- HASHMAP_ASSERT(map != NULL);
500
-
501
- if (!iter) {
502
- return NULL;
503
- }
504
- if (!entry->key) {
505
- /* Iterator is invalid, so just return the next valid entry */
506
- return hashmap_iter_next(map, iter);
507
- }
508
- hashmap_entry_remove(map, entry);
509
- return (struct hashmap_iter *)hashmap_entry_get_populated(map, entry);
510
- }
511
-
512
- /*
513
- * Return the key of the entry pointed to by the iterator.
514
- */
515
- const void *hashmap_iter_get_key(const struct hashmap_iter *iter)
516
- {
517
- if (!iter) {
518
- return NULL;
519
- }
520
- return (const void *)((struct hashmap_entry *)iter)->key;
521
- }
522
-
523
- /*
524
- * Return the data of the entry pointed to by the iterator.
525
- */
526
- void *hashmap_iter_get_data(const struct hashmap_iter *iter)
527
- {
528
- if (!iter) {
529
- return NULL;
530
- }
531
- return ((struct hashmap_entry *)iter)->data;
532
- }
533
-
534
- /*
535
- * Set the data pointer of the entry pointed to by the iterator.
536
- */
537
- void hashmap_iter_set_data(const struct hashmap_iter *iter, void *data)
538
- {
539
- if (!iter) {
540
- return;
541
- }
542
- ((struct hashmap_entry *)iter)->data = data;
543
- }
544
-
545
- /*
546
- * Invoke func for each entry in the hashmap. Unlike the hashmap_iter_*()
547
- * interface, this function supports calls to hashmap_remove() during iteration.
548
- * However, it is an error to put or remove an entry other than the current one,
549
- * and doing so will immediately halt iteration and return an error.
550
- * Iteration is stopped if func returns non-zero. Returns func's return
551
- * value if it is < 0, otherwise, 0.
552
- */
553
- int hashmap_foreach(const struct hashmap *map,
554
- int (*func)(const void *, void *, void *), void *arg)
555
- {
556
- struct hashmap_entry *entry;
557
- size_t num_entries;
558
- const void *key;
559
- int rc;
560
-
561
- HASHMAP_ASSERT(map != NULL);
562
- HASHMAP_ASSERT(func != NULL);
563
-
564
- entry = map->table;
565
- for (entry = map->table; entry < &map->table[map->table_size];
566
- ++entry) {
567
- if (!entry->key) {
568
- continue;
569
- }
570
- num_entries = map->num_entries;
571
- key = entry->key;
572
- rc = func(entry->key, entry->data, arg);
573
- if (rc < 0) {
574
- return rc;
575
- }
576
- if (rc > 0) {
577
- return 0;
578
- }
579
- /* Run this entry again if func() deleted it */
580
- if (entry->key != key) {
581
- --entry;
582
- } else if (num_entries != map->num_entries) {
583
- /* Stop immediately if func put/removed another entry */
584
- return -1;
585
- }
586
- }
587
- return 0;
588
- }
589
-
590
- /*
591
- * Default hash function for string keys.
592
- * This is an implementation of the well-documented Jenkins one-at-a-time
593
- * hash function.
594
- */
595
- size_t hashmap_hash_string(const void *key)
596
- {
597
- const char *key_str = (const char *)key;
598
- size_t hash = 0;
599
-
600
- for (; *key_str; ++key_str) {
601
- hash += *key_str;
602
- hash += (hash << 10);
603
- hash ^= (hash >> 6);
604
- }
605
- hash += (hash << 3);
606
- hash ^= (hash >> 11);
607
- hash += (hash << 15);
608
- return hash;
609
- }
610
-
611
- /*
612
- * Default key comparator function for string keys.
613
- */
614
- int hashmap_compare_string(const void *a, const void *b)
615
- {
616
- return strcmp((const char *)a, (const char *)b);
617
- }
618
-
619
- /*
620
- * Default key allocation function for string keys. Use free() for the
621
- * key_free_func.
622
- */
623
- void *hashmap_alloc_key_string(const void *key)
624
- {
625
- return (void *)strdup((const char *)key);
626
- }
627
-
628
- #ifdef HASHMAP_METRICS
629
- /*
630
- * Return the load factor.
631
- */
632
- double hashmap_load_factor(const struct hashmap *map)
633
- {
634
- HASHMAP_ASSERT(map != NULL);
635
-
636
- if (!map->table_size) {
637
- return 0;
638
- }
639
- return (double)map->num_entries / map->table_size;
640
- }
641
-
642
- /*
643
- * Return the average number of collisions per entry.
644
- */
645
- double hashmap_collisions_mean(const struct hashmap *map)
646
- {
647
- struct hashmap_entry *entry;
648
- size_t total_collisions = 0;
649
-
650
- HASHMAP_ASSERT(map != NULL);
651
-
652
- if (!map->num_entries) {
653
- return 0;
654
- }
655
- for (entry = map->table; entry < &map->table[map->table_size];
656
- ++entry) {
657
- if (!entry->key) {
658
- continue;
659
- }
660
- total_collisions += entry->num_collisions;
661
- }
662
- return (double)total_collisions / map->num_entries;
663
- }
664
-
665
- /*
666
- * Return the variance between entry collisions. The higher the variance,
667
- * the more likely the hash function is poor and is resulting in clustering.
668
- */
669
- double hashmap_collisions_variance(const struct hashmap *map)
670
- {
671
- struct hashmap_entry *entry;
672
- double mean_collisions;
673
- double variance;
674
- double total_variance = 0;
675
-
676
- HASHMAP_ASSERT(map != NULL);
677
-
678
- if (!map->num_entries) {
679
- return 0;
680
- }
681
- mean_collisions = hashmap_collisions_mean(map);
682
- for (entry = map->table; entry < &map->table[map->table_size];
683
- ++entry) {
684
- if (!entry->key) {
685
- continue;
686
- }
687
- variance = (double)entry->num_collisions - mean_collisions;
688
- total_variance += variance * variance;
689
- }
690
- return total_variance / map->num_entries;
691
- }
692
- #endif