1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or https://opensource.org/licenses/CDDL-1.0.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * Copyright (c) 2016, Intel Corporation.
27  */
28 
29 #include <assert.h>
30 #include <stddef.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/list.h>
34 #include <sys/time.h>
35 
36 #include "fmd_api.h"
37 #include "fmd_serd.h"
38 #include "../zed_log.h"
39 
40 
41 #define	FMD_STR_BUCKETS		211
42 
43 
44 #ifdef SERD_ENG_DEBUG
45 #define	serd_log_msg(fmt, ...) \
46 	zed_log_msg(LOG_INFO, fmt, __VA_ARGS__)
47 #else
48 #define	serd_log_msg(fmt, ...)
49 #endif
50 
51 
52 /*
53  * SERD Engine Backend
54  */
55 
56 /*
57  * Compute the delta between events in nanoseconds.  To account for very old
58  * events which are replayed, we must handle the case where time is negative.
59  * We convert the hrtime_t's to unsigned 64-bit integers and then handle the
60  * case where 'old' is greater than 'new' (i.e. high-res time has wrapped).
61  */
62 static hrtime_t
63 fmd_event_delta(hrtime_t t1, hrtime_t t2)
64 {
65 	uint64_t old = t1;
66 	uint64_t new = t2;
67 
68 	return (new >= old ? new - old : (UINT64_MAX - old) + new + 1);
69 }
70 
71 static fmd_serd_eng_t *
72 fmd_serd_eng_alloc(const char *name, uint64_t n, hrtime_t t)
73 {
74 	fmd_serd_eng_t *sgp;
75 
76 	sgp = malloc(sizeof (fmd_serd_eng_t));
77 	if (sgp == NULL) {
78 		perror("malloc");
79 		exit(EXIT_FAILURE);
80 	}
81 	memset(sgp, 0, sizeof (fmd_serd_eng_t));
82 
83 	sgp->sg_name = strdup(name);
84 	if (sgp->sg_name == NULL) {
85 		perror("strdup");
86 		exit(EXIT_FAILURE);
87 	}
88 
89 	sgp->sg_flags = FMD_SERD_DIRTY;
90 	sgp->sg_n = n;
91 	sgp->sg_t = t;
92 
93 	list_create(&sgp->sg_list, sizeof (fmd_serd_elem_t),
94 	    offsetof(fmd_serd_elem_t, se_list));
95 
96 	return (sgp);
97 }
98 
99 static void
100 fmd_serd_eng_free(fmd_serd_eng_t *sgp)
101 {
102 	fmd_serd_eng_reset(sgp);
103 	free(sgp->sg_name);
104 	list_destroy(&sgp->sg_list);
105 	free(sgp);
106 }
107 
108 /*
109  * sourced from fmd_string.c
110  */
111 static ulong_t
112 fmd_strhash(const char *key)
113 {
114 	ulong_t g, h = 0;
115 	const char *p;
116 
117 	for (p = key; *p != '\0'; p++) {
118 		h = (h << 4) + *p;
119 
120 		if ((g = (h & 0xf0000000)) != 0) {
121 			h ^= (g >> 24);
122 			h ^= g;
123 		}
124 	}
125 
126 	return (h);
127 }
128 
129 void
130 fmd_serd_hash_create(fmd_serd_hash_t *shp)
131 {
132 	shp->sh_hashlen = FMD_STR_BUCKETS;
133 	shp->sh_hash = calloc(shp->sh_hashlen, sizeof (void *));
134 	shp->sh_count = 0;
135 
136 	if (shp->sh_hash == NULL) {
137 		perror("calloc");
138 		exit(EXIT_FAILURE);
139 	}
140 
141 }
142 
143 void
144 fmd_serd_hash_destroy(fmd_serd_hash_t *shp)
145 {
146 	fmd_serd_eng_t *sgp, *ngp;
147 	uint_t i;
148 
149 	for (i = 0; i < shp->sh_hashlen; i++) {
150 		for (sgp = shp->sh_hash[i]; sgp != NULL; sgp = ngp) {
151 			ngp = sgp->sg_next;
152 			fmd_serd_eng_free(sgp);
153 		}
154 	}
155 
156 	free(shp->sh_hash);
157 	memset(shp, 0, sizeof (fmd_serd_hash_t));
158 }
159 
160 void
161 fmd_serd_hash_apply(fmd_serd_hash_t *shp, fmd_serd_eng_f *func, void *arg)
162 {
163 	fmd_serd_eng_t *sgp;
164 	uint_t i;
165 
166 	for (i = 0; i < shp->sh_hashlen; i++) {
167 		for (sgp = shp->sh_hash[i]; sgp != NULL; sgp = sgp->sg_next)
168 			func(sgp, arg);
169 	}
170 }
171 
172 fmd_serd_eng_t *
173 fmd_serd_eng_insert(fmd_serd_hash_t *shp, const char *name,
174     uint_t n, hrtime_t t)
175 {
176 	uint_t h = fmd_strhash(name) % shp->sh_hashlen;
177 	fmd_serd_eng_t *sgp = fmd_serd_eng_alloc(name, n, t);
178 
179 	serd_log_msg("  SERD Engine: inserting  %s N %d T %llu",
180 	    name, (int)n, (long long unsigned)t);
181 
182 	sgp->sg_next = shp->sh_hash[h];
183 	shp->sh_hash[h] = sgp;
184 	shp->sh_count++;
185 
186 	return (sgp);
187 }
188 
189 fmd_serd_eng_t *
190 fmd_serd_eng_lookup(fmd_serd_hash_t *shp, const char *name)
191 {
192 	uint_t h = fmd_strhash(name) % shp->sh_hashlen;
193 	fmd_serd_eng_t *sgp;
194 
195 	for (sgp = shp->sh_hash[h]; sgp != NULL; sgp = sgp->sg_next) {
196 		if (strcmp(name, sgp->sg_name) == 0)
197 			return (sgp);
198 	}
199 
200 	return (NULL);
201 }
202 
203 void
204 fmd_serd_eng_delete(fmd_serd_hash_t *shp, const char *name)
205 {
206 	uint_t h = fmd_strhash(name) % shp->sh_hashlen;
207 	fmd_serd_eng_t *sgp, **pp = &shp->sh_hash[h];
208 
209 	serd_log_msg("  SERD Engine: deleting %s", name);
210 
211 	for (sgp = *pp; sgp != NULL; sgp = sgp->sg_next) {
212 		if (strcmp(sgp->sg_name, name) != 0)
213 			pp = &sgp->sg_next;
214 		else
215 			break;
216 	}
217 
218 	if (sgp != NULL) {
219 		*pp = sgp->sg_next;
220 		fmd_serd_eng_free(sgp);
221 		assert(shp->sh_count != 0);
222 		shp->sh_count--;
223 	}
224 }
225 
226 static void
227 fmd_serd_eng_discard(fmd_serd_eng_t *sgp, fmd_serd_elem_t *sep)
228 {
229 	list_remove(&sgp->sg_list, sep);
230 	sgp->sg_count--;
231 
232 	serd_log_msg("  SERD Engine: discarding %s, %d remaining",
233 	    sgp->sg_name, (int)sgp->sg_count);
234 
235 	free(sep);
236 }
237 
238 int
239 fmd_serd_eng_record(fmd_serd_eng_t *sgp, hrtime_t hrt)
240 {
241 	fmd_serd_elem_t *sep, *oep;
242 
243 	/*
244 	 * If the fired flag is already set, return false and discard the
245 	 * event.  This means that the caller will only see the engine "fire"
246 	 * once until fmd_serd_eng_reset() is called.  The fmd_serd_eng_fired()
247 	 * function can also be used in combination with fmd_serd_eng_record().
248 	 */
249 	if (sgp->sg_flags & FMD_SERD_FIRED) {
250 		serd_log_msg("  SERD Engine: record %s already fired!",
251 		    sgp->sg_name);
252 		return (B_FALSE);
253 	}
254 
255 	while (sgp->sg_count >= sgp->sg_n)
256 		fmd_serd_eng_discard(sgp, list_tail(&sgp->sg_list));
257 
258 	sep = malloc(sizeof (fmd_serd_elem_t));
259 	if (sep == NULL) {
260 		perror("malloc");
261 		exit(EXIT_FAILURE);
262 	}
263 	sep->se_hrt = hrt;
264 
265 	list_insert_head(&sgp->sg_list, sep);
266 	sgp->sg_count++;
267 
268 	serd_log_msg("  SERD Engine: recording %s of %d (%llu)",
269 	    sgp->sg_name, (int)sgp->sg_count, (long long unsigned)hrt);
270 
271 	/*
272 	 * Pick up the oldest element pointer for comparison to 'sep'.  We must
273 	 * do this after adding 'sep' because 'oep' and 'sep' can be the same.
274 	 */
275 	oep = list_tail(&sgp->sg_list);
276 
277 	if (sgp->sg_count >= sgp->sg_n &&
278 	    fmd_event_delta(oep->se_hrt, sep->se_hrt) <= sgp->sg_t) {
279 		sgp->sg_flags |= FMD_SERD_FIRED | FMD_SERD_DIRTY;
280 		serd_log_msg("  SERD Engine: fired %s", sgp->sg_name);
281 		return (B_TRUE);
282 	}
283 
284 	sgp->sg_flags |= FMD_SERD_DIRTY;
285 	return (B_FALSE);
286 }
287 
288 int
289 fmd_serd_eng_fired(fmd_serd_eng_t *sgp)
290 {
291 	return (sgp->sg_flags & FMD_SERD_FIRED);
292 }
293 
294 int
295 fmd_serd_eng_empty(fmd_serd_eng_t *sgp)
296 {
297 	return (sgp->sg_count == 0);
298 }
299 
300 void
301 fmd_serd_eng_reset(fmd_serd_eng_t *sgp)
302 {
303 	serd_log_msg("  SERD Engine: resetting %s", sgp->sg_name);
304 
305 	while (sgp->sg_count != 0)
306 		fmd_serd_eng_discard(sgp, list_head(&sgp->sg_list));
307 
308 	sgp->sg_flags &= ~FMD_SERD_FIRED;
309 	sgp->sg_flags |= FMD_SERD_DIRTY;
310 }
311 
312 void
313 fmd_serd_eng_gc(fmd_serd_eng_t *sgp, void *arg)
314 {
315 	(void) arg;
316 	fmd_serd_elem_t *sep, *nep;
317 	hrtime_t hrt;
318 
319 	if (sgp->sg_count == 0 || (sgp->sg_flags & FMD_SERD_FIRED))
320 		return; /* no garbage collection needed if empty or fired */
321 
322 	sep = list_head(&sgp->sg_list);
323 	if (sep == NULL)
324 		return;
325 
326 	hrt = sep->se_hrt - sgp->sg_t;
327 
328 	for (sep = list_head(&sgp->sg_list); sep != NULL; sep = nep) {
329 		if (sep->se_hrt >= hrt)
330 			break; /* sep and subsequent events are all within T */
331 
332 		nep = list_next(&sgp->sg_list, sep);
333 		fmd_serd_eng_discard(sgp, sep);
334 		sgp->sg_flags |= FMD_SERD_DIRTY;
335 	}
336 }
337