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 */
62static hrtime_t
63fmd_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
71static fmd_serd_eng_t *
72fmd_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
99static void
100fmd_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 */
111static ulong_t
112fmd_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
129void
130fmd_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
143void
144fmd_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
160void
161fmd_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
172fmd_serd_eng_t *
173fmd_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
189fmd_serd_eng_t *
190fmd_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
203void
204fmd_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
226static void
227fmd_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
238int
239fmd_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
288int
289fmd_serd_eng_fired(fmd_serd_eng_t *sgp)
290{
291	return (sgp->sg_flags & FMD_SERD_FIRED);
292}
293
294int
295fmd_serd_eng_empty(fmd_serd_eng_t *sgp)
296{
297	return (sgp->sg_count == 0);
298}
299
300void
301fmd_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
312void
313fmd_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