1(*
2 * Copyright (C) 2006-2007 XenSource Ltd.
3 * Copyright (C) 2008      Citrix Ltd.
4 * Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation; version 2.1 only. with the special
9 * exception on linking described in file LICENSE.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU Lesser General Public License for more details.
15 *)
16
17let debug fmt = Logging.debug "domains" fmt
18let error fmt = Logging.error "domains" fmt
19let warn fmt  = Logging.warn  "domains" fmt
20
21type domains = {
22	eventchn: Event.t;
23	table: (Xenctrl.domid, Domain.t) Hashtbl.t;
24
25	(* N.B. the Queue module is not thread-safe but oxenstored is single-threaded. *)
26	(* Domains queue up to regain conflict-credit; we have a queue for
27	   domains that are carrying some penalty and so are below the
28	   maximum credit, and another queue for domains that have run out of
29	   credit and so have had their access paused. *)
30	doms_conflict_paused: (Domain.t option ref) Queue.t;
31	doms_with_conflict_penalty: (Domain.t option ref) Queue.t;
32
33	(* A callback function to be called when we go from zero to one paused domain.
34	   This will be to reset the countdown until the next unit of credit is issued. *)
35	on_first_conflict_pause: unit -> unit;
36
37	(* If config is set to use individual instead of aggregate conflict-rate-limiting,
38	   we use these counts instead of the queues. The second one includes the first. *)
39	mutable n_paused: int;    (* Number of domains with zero or negative credit *)
40	mutable n_penalised: int; (* Number of domains with less than maximum credit *)
41}
42
43let init eventchn on_first_conflict_pause = {
44	eventchn = eventchn;
45	table = Hashtbl.create 10;
46	doms_conflict_paused = Queue.create ();
47	doms_with_conflict_penalty = Queue.create ();
48	on_first_conflict_pause = on_first_conflict_pause;
49	n_paused = 0;
50	n_penalised = 0;
51}
52let del doms id = Hashtbl.remove doms.table id
53let exist doms id = Hashtbl.mem doms.table id
54let find doms id = Hashtbl.find doms.table id
55let number doms = Hashtbl.length doms.table
56let iter doms fct = Hashtbl.iter (fun _ b -> fct b) doms.table
57
58let rec is_empty_queue q =
59	Queue.is_empty q ||
60		if !(Queue.peek q) = None
61		then (
62			ignore (Queue.pop q);
63			is_empty_queue q
64		) else false
65
66let all_at_max_credit doms =
67	if !Define.conflict_rate_limit_is_aggregate
68	then
69		(* Check both becuase if burst limit is 1.0 then a domain can go straight
70		 * from max-credit to paused without getting into the penalty queue. *)
71		is_empty_queue doms.doms_with_conflict_penalty
72		&& is_empty_queue doms.doms_conflict_paused
73	else doms.n_penalised = 0
74
75(* Functions to handle queues of domains given that the domain might be deleted while in a queue. *)
76let push dom queue =
77	Queue.push (ref (Some dom)) queue
78
79let rec pop queue =
80	match !(Queue.pop queue) with
81	| None -> pop queue
82	| Some x -> x
83
84let remove_from_queue dom queue =
85	Queue.iter (fun d -> match !d with
86		| None -> ()
87		| Some x -> if x=dom then d := None) queue
88
89let cleanup xc doms =
90	let notify = ref false in
91	let dead_dom = ref [] in
92
93	Hashtbl.iter (fun id _ -> if id <> 0 then
94		try
95			let info = Xenctrl.domain_getinfo xc id in
96			if info.Xenctrl.shutdown || info.Xenctrl.dying then (
97				debug "Domain %u died (dying=%b, shutdown %b -- code %d)"
98				                    id info.Xenctrl.dying info.Xenctrl.shutdown info.Xenctrl.shutdown_code;
99				if info.Xenctrl.dying then
100					dead_dom := id :: !dead_dom
101				else
102					notify := true;
103			)
104		with Xenctrl.Error _ ->
105			debug "Domain %u died -- no domain info" id;
106			dead_dom := id :: !dead_dom;
107		) doms.table;
108	List.iter (fun id ->
109		let dom = Hashtbl.find doms.table id in
110		Domain.close dom;
111		Hashtbl.remove doms.table id;
112		if dom.Domain.conflict_credit <= !Define.conflict_burst_limit
113		then (
114			remove_from_queue dom doms.doms_with_conflict_penalty;
115			if (dom.Domain.conflict_credit <= 0.) then remove_from_queue dom doms.doms_conflict_paused
116		)
117	) !dead_dom;
118	!notify, !dead_dom
119
120let resume doms domid =
121	()
122
123let create xc doms domid mfn port =
124	let interface = Xenctrl.map_foreign_range xc domid (Xenmmap.getpagesize()) mfn in
125	let dom = Domain.make domid mfn port interface doms.eventchn in
126	Hashtbl.add doms.table domid dom;
127	Domain.bind_interdomain dom;
128	dom
129
130let xenstored_kva = ref ""
131let xenstored_port = ref ""
132
133let create0 doms =
134	let port, interface =
135		(
136			let port = Utils.read_file_single_integer !xenstored_port
137			and fd = Unix.openfile !xenstored_kva
138					       [ Unix.O_RDWR ] 0o600 in
139			let interface = Xenmmap.mmap fd Xenmmap.RDWR Xenmmap.SHARED
140						  (Xenmmap.getpagesize()) 0 in
141			Unix.close fd;
142			port, interface
143		)
144		in
145	let dom = Domain.make 0 Nativeint.zero port interface doms.eventchn in
146	Hashtbl.add doms.table 0 dom;
147	Domain.bind_interdomain dom;
148	Domain.notify dom;
149	dom
150
151let decr_conflict_credit doms dom =
152	dom.Domain.caused_conflicts <- Int64.add 1L dom.Domain.caused_conflicts;
153	let before = dom.Domain.conflict_credit in
154	let after = max (-1.0) (before -. 1.0) in
155	debug "decr_conflict_credit dom%d %F -> %F" (Domain.get_id dom) before after;
156	dom.Domain.conflict_credit <- after;
157	let newly_penalised =
158		before >= !Define.conflict_burst_limit
159		&& after < !Define.conflict_burst_limit in
160	let newly_paused = before > 0.0 && after <= 0.0 in
161	if !Define.conflict_rate_limit_is_aggregate then (
162		if newly_penalised
163		&& after > 0.0
164		then (
165			push dom doms.doms_with_conflict_penalty
166		) else if newly_paused
167		then (
168			let first_pause = Queue.is_empty doms.doms_conflict_paused in
169			push dom doms.doms_conflict_paused;
170			if first_pause then doms.on_first_conflict_pause ()
171		) else (
172			(* The queues are correct already: no further action needed. *)
173		)
174	) else (
175		if newly_penalised then doms.n_penalised <- doms.n_penalised + 1;
176		if newly_paused then (
177			doms.n_paused <- doms.n_paused + 1;
178			if doms.n_paused = 1 then doms.on_first_conflict_pause ()
179		)
180	)
181
182(* Give one point of credit to one domain, and update the queues appropriately. *)
183let incr_conflict_credit_from_queue doms =
184	let process_queue q requeue_test =
185		let d = pop q in
186		let before = d.Domain.conflict_credit in (* just for debug-logging *)
187		d.Domain.conflict_credit <- min (d.Domain.conflict_credit +. 1.0) !Define.conflict_burst_limit;
188		debug "incr_conflict_credit_from_queue: dom%d: %F -> %F" (Domain.get_id d) before d.Domain.conflict_credit;
189		if requeue_test d.Domain.conflict_credit then (
190			push d q (* Make it queue up again for its next point of credit. *)
191		)
192	in
193	let paused_queue_test cred = cred <= 0.0 in
194	let penalty_queue_test cred = cred < !Define.conflict_burst_limit in
195	try process_queue doms.doms_conflict_paused paused_queue_test
196	with Queue.Empty -> (
197		try process_queue doms.doms_with_conflict_penalty penalty_queue_test
198		with Queue.Empty -> () (* Both queues are empty: nothing to do here. *)
199	)
200
201let incr_conflict_credit doms =
202	if !Define.conflict_rate_limit_is_aggregate
203	then incr_conflict_credit_from_queue doms
204	else (
205		(* Give a point of credit to every domain, subject only to the cap. *)
206		let inc dom =
207			let before = dom.Domain.conflict_credit in
208			let after = min (before +. 1.0) !Define.conflict_burst_limit in
209			dom.Domain.conflict_credit <- after;
210			debug "incr_conflict_credit dom%d: %F -> %F" (Domain.get_id dom) before after;
211
212			if before <= 0.0 && after > 0.0
213			then doms.n_paused <- doms.n_paused - 1;
214
215			if before < !Define.conflict_burst_limit
216			&& after >= !Define.conflict_burst_limit
217			then doms.n_penalised <- doms.n_penalised - 1
218		in
219		if doms.n_penalised > 0 then iter doms inc
220	)
221