summaryrefslogtreecommitdiffstats
path: root/virt-ctrl/vc_connections.ml
blob: 5525f5e556967f0de369cb81fab637e6c2a880c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
(* virt-ctrl: A graphical management tool.
   (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc.
   http://libvirt.org/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*)

open Printf

module C = Libvirt.Connect
module D = Libvirt.Domain
module N = Libvirt.Network

open Vc_helpers

(* List of currently open connections.  Actually it's a list of
 * (id, Libvirt.Connect.t) so that we can easily identify
 * connections by their unique ID.
 *)
let get_conns, add_conn, del_conn =
  let conns = ref [] in
  let id = ref 0 in
  let get_conns () = !conns in
  let add_conn conn =
    incr id; let id = !id in
    conns := (id, conn) :: !conns;
    id
  in
  let del_conn id =
    conns := List.filter (fun (id', _) -> id <> id') !conns
  in
  get_conns, add_conn, del_conn

(* The current state.  This is used so that we can see changes that
 * have happened and add or remove parts of the model.  (Previously
 * we used to recreate the whole model each time, but the problem
 * with that is we "forget" things like the selection).
 *)
type state = connection list
and connection = int (* connection ID *) * (active list * inactive list)
and active = int (* domain's ID *)
and inactive = string (* domain's name *)

(* The last "CPU time" seen for a domain, so we can calculate CPU % usage.
 * Hash of (connid, domid) -> cpu_time [int64].
 *)
let last_cpu_time = Hashtbl.create 13
let last_time = ref (Unix.gettimeofday ())

type columns = string GTree.column * string GTree.column * string GTree.column * string GTree.column * string GTree.column * int GTree.column

let debug_repopulate = true

(* Populate the tree with the current list of connections, domains.
 * This function is called once per second.
 *)
let repopulate (tree : GTree.view) (model : GTree.tree_store)
    (col_name_id, col_domname, col_status, col_cpu, col_mem, col_id)
    state =
  let time_passed =
    let time_now = Unix.gettimeofday () in
    let time_passed = time_now -. !last_time in
    last_time := time_now;
    time_passed in

  (* Which connections have been added or removed? *)
  let conns = get_conns () in
  let added, _, removed =
    let old_conn_ids = List.map fst state
    and new_conn_ids = List.map fst conns in
    differences old_conn_ids new_conn_ids in

  (* Remove the subtrees for any connections which have gone. *)
  if debug_repopulate then List.iter (eprintf "-connection %d\n%!") removed;

  List.iter (
    fun conn_id ->
      filter_top_level_rows model
	(fun row -> conn_id <> model#get ~row ~column:col_id)
  ) removed;

  (* Add placeholder subtree for any new connections. *)
  if debug_repopulate then List.iter (eprintf "+connection %d\n%!") added;

  List.iter (
    fun conn_id ->
      let row = model#append () in
      (* Get the connection name. *)
      let name =
	try C.get_hostname (List.assoc conn_id conns)
	with Not_found | Libvirt.Virterror _ ->
	  "Conn #" ^ string_of_int conn_id in
      model#set ~row ~column:col_name_id name;
      model#set ~row ~column:col_id conn_id;
      (* XXX This doesn't work, why? *)
      tree#expand_row (model#get_path row)
  ) added;

  let new_state =
    List.map (
      fun (conn_id, conn) ->
	(* Get the old list of active and inactive domains.  If this
	 * connection is newly created, start with empty lists.
	 *)
	let old_active, old_inactive =
	  try List.assoc conn_id state
	  with Not_found -> [], [] in

	(* Get the top level row in the model corresponding to this
	 * connection.
	 *)
	let parent =
	  try find_top_level_row model
	    (fun row -> conn_id = model#get ~row ~column:col_id)
	  with Not_found -> assert false (* Should never happen. *) in

	try
	  (* Node info & number of CPUs available. *)
	  let node_info = C.get_node_info conn in
	  let nr_cpus = C.maxcpus_of_node_info node_info in

	  (* For this connection, get a current list of active domains (IDs) *)
	  let active =
	    let n = C.num_of_domains conn in
	    let doms = C.list_domains conn n in
	    Array.to_list doms in

	  (* Which active domains have been added or removed? *)
	  let added, _, removed = differences old_active active in

	  (* Remove any active domains which have disappeared. *)
	  if debug_repopulate then
	    List.iter (eprintf "-active %d\n%!") removed;

	  List.iter (
	    fun domid ->
	      filter_rows model
		(fun row -> domid <> model#get ~row ~column:col_id)
		(model#iter_children (Some parent))
	  ) removed;

	  (* Add any active domains which have appeared. *)
	  if debug_repopulate then
	    List.iter (eprintf "+active %d\n%!") added;

	  List.iter (
	    fun domid ->
	      let domname =
		try
		  let dom = D.lookup_by_id conn domid in
		  D.get_name dom
		with _ -> "" in (* Ignore any transient error. *)

	      let row = model#append ~parent () in
	      model#set ~row ~column:col_name_id (string_of_int domid);
	      model#set ~row ~column:col_domname domname;
	      model#set ~row ~column:col_id domid
	  ) added;

	  (* Get a current list of inactive domains (names). *)
	  let inactive =
	    let n = C.num_of_defined_domains conn in
	    let doms = C.list_defined_domains conn n in
	    Array.to_list doms in

	  (* Which inactive domains have been added or removed? *)
	  let added, _, removed = differences old_inactive inactive in

	  (* Remove any inactive domains which have disappeared. *)
	  if debug_repopulate then
	    List.iter (eprintf "-inactive %s\n%!") removed;

	  List.iter (
	    fun domname ->
	      filter_rows model
		(fun row ->
		   model#get ~row ~column:col_id <> -1 ||
		   model#get ~row ~column:col_domname <> domname)
		(model#iter_children (Some parent))
	  ) removed;

	  (* Add any inactive domains which have appeared. *)
	  if debug_repopulate then
	    List.iter (eprintf "+inactive %s\n%!") added;

	  List.iter (
	    fun domname ->
	      let row = model#append ~parent () in
	      model#set ~row ~column:col_name_id "";
	      model#set ~row ~column:col_domname domname;
	      model#set ~row ~column:col_status "inactive";
	      model#set ~row ~column:col_id (-1)
	  ) added;

	  (* Now iterate over all active domains and update their state,
	   * CPU and memory.
	   *)
	  iter_rows model (
	    fun row ->
	      let domid = model#get ~row ~column:col_id in
	      if domid >= 0 then ( (* active *)
		try
		  let dom = D.lookup_by_id conn domid in
		  let info = D.get_info dom in
		  let status = string_of_domain_state info.D.state in
		  model#set ~row ~column:col_status status;
		  let memory = sprintf "%Ld K" info.D.memory in
		  model#set ~row ~column:col_mem memory;

		  let ns_now = info.D.cpu_time in (* ns = nanoseconds *)
		  let ns_prev =
		    try
		      let ns = Hashtbl.find last_cpu_time (conn_id, domid) in
		      if ns > ns_now then 0L else ns (* Rebooted? *)
		    with Not_found -> 0L in
		  Hashtbl.replace last_cpu_time (conn_id, domid) ns_now;
		  let ns_now = Int64.to_float ns_now in
		  let ns_prev = Int64.to_float ns_prev in
		  let ns_used = ns_now -. ns_prev in
		  let ns_available = 1_000_000_000. *. float nr_cpus in
		  let cpu_percent =
		    100. *. (ns_used /. ns_available) /. time_passed in
		  let cpu_percent = sprintf "%.1f %%" cpu_percent in
		  model#set ~row ~column:col_cpu cpu_percent;

		with Libvirt.Virterror _ -> () (* Ignore any transient error *)
	      )
	  ) (model#iter_children (Some parent));

	  (* Return new state. *)
	  conn_id, (active, inactive)
	with
	(* Libvirt errors here are not really fatal.  They can happen
	 * if the state changes at the moment we read it.  If it does
	 * happen, just return the old state, and next time we come
	 * around to this connection it'll be fixed.
	 *)
	| Libvirt.Virterror err ->
	    prerr_endline (Libvirt.Virterror.to_string err);
	    conn_id, (old_active, old_inactive)
	| Failure msg ->
	    prerr_endline msg;
	    conn_id, (old_active, old_inactive)
    ) conns in

  (* Return the updated state. *)
  new_state

(* Make the treeview which displays the connections and domains. *)
let make_treeview ?packing () =
  let cols = new GTree.column_list in
  let col_name_id = cols#add Gobject.Data.string in
  let col_domname = cols#add Gobject.Data.string in
  let col_status = cols#add Gobject.Data.string in
  let col_cpu = cols#add Gobject.Data.string in
  let col_mem = cols#add Gobject.Data.string in
  (* Hidden column containing the connection ID or domain ID.  For
   * inactive domains, this contains -1 and col_domname is the name. *)
  let col_id = cols#add Gobject.Data.int in
  let model = GTree.tree_store cols in

  (* Column sorting functions. *)
  let make_sort_func_on column =
    fun (model : GTree.model) row1 row2 ->
      let col1 = model#get ~row:row1 ~column in
      let col2 = model#get ~row:row2 ~column in
      compare col1 col2
  in
  (*model#set_default_sort_func (make_sort_func_on col_domname);*)
  model#set_sort_func 0 (make_sort_func_on col_name_id);
  model#set_sort_func 1 (make_sort_func_on col_domname);
  model#set_sort_column_id 1 `ASCENDING;

  (* Make the GtkTreeView and attach column renderers to it. *)
  let tree = GTree.view ~model ~reorderable:false ?packing () in

  let append_visible_column title column sort =
    let renderer = GTree.cell_renderer_text [], ["text", column] in
    let view_col = GTree.view_column ~title ~renderer () in
    ignore (tree#append_column view_col);
    match sort with
    | None -> ()
    | Some (sort_indicator, sort_order, sort_column_id) ->
	view_col#set_sort_indicator sort_indicator;
	view_col#set_sort_order sort_order;
	view_col#set_sort_column_id sort_column_id
  in
  append_visible_column "ID" col_name_id (Some (false, `ASCENDING, 0));
  append_visible_column "Name" col_domname (Some (true, `ASCENDING, 1));
  append_visible_column "Status" col_status None;
  append_visible_column "CPU" col_cpu None;
  append_visible_column "Memory" col_mem None;

  let columns =
    col_name_id, col_domname, col_status, col_cpu, col_mem, col_id in
  let state = repopulate tree model columns [] in

  (tree, model, columns, state)

(* Callback function to open a connection.
 * This should be a lot more sophisticated. XXX
 *)
let open_connection () =
  let title = "Open connection to hypervisor" in
  let name =
    GToolbox.input_string ~title ~text:"xen:///" ~ok:"Open" "Connection:" in
  match name with
  | None -> ()
  | Some name ->
      (* If this fails, let the exception escape and be printed
       * in the global exception handler.
       *)
      let conn = C.connect ~name () in
      ignore (add_conn conn)