/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Windows.Forms; using XenAdmin.Controls.Ballooning; using XenAdmin.Core; using XenAdmin.Network; using XenAPI; namespace XenAdmin.TabPages { public partial class BallooningPage : BaseTabPage { private const int ROW_GAP = 10; public BallooningPage() { InitializeComponent(); // http://alt.pluralsight.com/wiki/default.aspx/Craig.FlickerFreeControlDrawing SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque | ControlStyles.UserPaint, true); Text = Messages.DYNAMIC_MEMORY_CONTROL; Host_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(Host_CollectionChanged); VM_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(VM_CollectionChanged); } IXenObject xenObject; List hosts = new List(); List vms = new List(); private readonly CollectionChangeEventHandler Host_CollectionChangedWithInvoke; //solution from: http://stackoverflow.com/questions/2612487/how-to-fix-the-flickering-in-user-controls protected override CreateParams CreateParams { get { var cp = base.CreateParams; cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED return cp; } } public IXenObject XenObject { set { System.Diagnostics.Trace.Assert(value is Pool || value is Host || value is VM); xenObject = value; UnregisterHandlers(); vms.Clear(); hosts.Clear(); if (value is VM) vms.Add((VM)value); else if (value is Host) { Host host = value as Host; hosts.Add(host); foreach (VM vm in host.Connection.Cache.VMs) { if (VMWanted(vm, host)) vms.Add(vm); } } else // value is XenObject { value.Connection.Cache.RegisterCollectionChanged(Host_CollectionChangedWithInvoke); hosts.AddRange(value.Connection.Cache.Hosts); } value.Connection.XenObjectsUpdated += XenObjectsUpdated; foreach (Host host in hosts) RegisterHostHandlers(host); foreach (VM vm in vms) RegisterVMHandlers(vm); Program.BeginInvoke(Program.MainWindow, () => { Rebuild(); }); } } private bool VMWanted(VM vm, Host host) { return vm.is_a_real_vm() && vm.Show(Properties.Settings.Default.ShowHiddenVMs) && vm.Home() == host; } private readonly CollectionChangeEventHandler VM_CollectionChangedWithInvoke; private void RegisterHostHandlers(Host host) { host.PropertyChanged += host_PropertyChanged; host.Connection.Cache.RegisterCollectionChanged(VM_CollectionChangedWithInvoke); foreach (VM vm in host.Connection.Cache.VMs) RegisterAllVMHandlers(vm); } private void host_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "name_label" && hosts.Count >= 2) _rebuild_needed = true; // might change the sort order } private void VM_CollectionChanged(object sender, CollectionChangeEventArgs e) { if (e.Action == CollectionChangeAction.Remove) { VM vm = e.Element as VM; UnregisterVMHandlers(vm); } XenObject = xenObject; // have to recalculate list of VMs etc. } private void Host_CollectionChanged(object sender, CollectionChangeEventArgs e) { if (e.Action == CollectionChangeAction.Remove) { Host host = e.Element as Host; UnregisterHostHandlers(host); } XenObject = xenObject; } private void RegisterVMHandlers(VM vm) { vm.PropertyChanged -= vm_PropertyChanged; vm.PropertyChanged += vm_PropertyChanged; RegisterVMGuestMetrics(vm); } private void RegisterAllVMHandlers(VM vm) { vm.PropertyChanged -= vm_PropertyChanged_allVMs; vm.PropertyChanged += vm_PropertyChanged_allVMs; } private void RegisterVMGuestMetrics(VM vm) { VM_guest_metrics guest_metrics = vm.Connection.Resolve(vm.guest_metrics); if (guest_metrics != null) { guest_metrics.PropertyChanged -= guest_metrics_PropertyChanged; guest_metrics.PropertyChanged += guest_metrics_PropertyChanged; } } private void UnregisterHandlers() { foreach (Host host in hosts) UnregisterHostHandlers(host); if (xenObject != null) { Pool pool = xenObject as Pool; if (pool != null) pool.Connection.Cache.DeregisterCollectionChanged(Host_CollectionChangedWithInvoke); xenObject.Connection.XenObjectsUpdated -= XenObjectsUpdated; foreach (VM vm in xenObject.Connection.Cache.VMs) UnregisterVMHandlers(vm); } foreach (VM vm in vms) UnregisterVMHandlers(vm); // Should duplicate above line, but let's be safe foreach (Control c in pageContainerPanel.Controls) { VMMemoryRow vmRow = c as VMMemoryRow; if (vmRow != null) { vmRow.UnregisterHandlers(); continue; } HostMemoryRow hostRow = c as HostMemoryRow; if (hostRow != null) { hostRow.UnregisterHandlers(); } } } private void UnregisterHostHandlers(Host host) { host.Connection.Cache.DeregisterCollectionChanged(VM_CollectionChangedWithInvoke); } private void UnregisterVMHandlers(VM vm) { vm.PropertyChanged -= vm_PropertyChanged; vm.PropertyChanged -= vm_PropertyChanged_allVMs; VM_guest_metrics guest_metrics = vm.Connection.Resolve(vm.guest_metrics); if (guest_metrics != null) guest_metrics.PropertyChanged -= guest_metrics_PropertyChanged; } public override void PageHidden() { UnregisterHandlers(); } private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "guest_metrics") RegisterVMGuestMetrics((VM)sender); if (e.PropertyName == "memory_static_min" || e.PropertyName == "memory_static_max" || e.PropertyName == "memory_dynamic_min" || e.PropertyName == "memory_dynamic_max" || e.PropertyName == "metrics" || e.PropertyName == "guest_metrics") { // We could just redraw the row for this VM (and for the host), provided it doesn't share a row // with any other VMs before or after. But doesn't seem necessary to figure all that out. _rebuild_needed = true; } } private void vm_PropertyChanged_allVMs(object sender, PropertyChangedEventArgs e) { // Only observe real VMs (but templates come through here too because // they change into real VMs during VM creation). if (!((VM)sender).is_a_real_vm()) return; // These are used by MainWindow.VMHome() to determine which host the VM belongs to if (e.PropertyName == "power_state" || e.PropertyName == "VBDs" || e.PropertyName == "affinity" || // These can change whether the VM is shown e.PropertyName == "name_label" || e.PropertyName == "other_config"|| e.PropertyName=="resident_on") { if (xenObject is Pool) { XenObject = xenObject; // have to recalculate list of VMs etc. return; } if (xenObject is Host) { bool vmWanted = VMWanted((VM)sender, (Host)xenObject); if (vmWanted != vms.Contains((VM)sender)) { XenObject = xenObject; // have to recalculate list of VMs etc. return; } } // We also have to redisplay if the power_state or name_label changes (can change the sort order) if ((e.PropertyName == "power_state" || e.PropertyName == "name_label") && vms.Contains((VM)sender)) _rebuild_needed = true; } } private void guest_metrics_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "other") _rebuild_needed = true; } // Whether a change to the properties of any object means we need to Rebuild. // This is in order to batch up changes to several objects at once (see CA-35330). private bool _rebuild_needed = false; private void XenObjectsUpdated(object sender, EventArgs e) { if (_rebuild_needed) Rebuild(); } private bool _rebuilding = false; private void Rebuild() { Program.AssertOnEventThread(); _rebuild_needed = false; if (!this.Visible) return; _rebuilding = true; pageContainerPanel.SuspendLayout(); // Store a list of the current controls. We remove them at the end because it makes less flicker that way. // While we're going through them, remember which VMs were on expanded rows. List oldControls = new List(pageContainerPanel.Controls.Count); List expandedVMs = new List(vms.Count); foreach (Control c in pageContainerPanel.Controls) { oldControls.Add(c); VMMemoryRow vmRow = c as VMMemoryRow; if (vmRow != null && vmRow.Expanded) expandedVMs.AddRange(vmRow.VMs); } // Group VMs with the same settings Dictionary> settingsToVMs = new Dictionary>(); // all VMs with a particular setting List listSettings = new List(); // also make a list of MemSettings to preserve the order vms.Sort(); foreach (VM vm in vms) { MemSettings settings = vm.has_ballooning() ? new MemSettings(true, vm.power_state, vm.memory_static_min, vm.memory_static_max, vm.memory_dynamic_min, vm.memory_dynamic_max) : new MemSettings(false, vm.power_state, 0, vm.memory_static_max, 0, 0); // don't consider other mem settings if ballooning off if (!settingsToVMs.ContainsKey(settings)) // we've not seen these settings on another VM { settingsToVMs.Add(settings, new List()); listSettings.Add(settings); } settingsToVMs[settings].Add(vm); } // Add server rows int initScroll = pageContainerPanel.VerticalScroll.Value; int top = pageContainerPanel.Padding.Top - initScroll; hosts.Sort(); foreach (Host host in hosts) { Host_metrics metrics = host.Connection.Resolve(host.metrics); if (metrics == null || !metrics.live) continue; AddRowToPanel(new HostMemoryRow(host), ref top); } // Add VM rows. // Sort the rows first by power state then by usual sort order of first VM (because of vms.Sort() above). // Easier to traverse listSettings five times, but more complicated sorts could be achieved by listSettings.Sort(). vm_power_state[] power_state_order = {vm_power_state.Running, vm_power_state.Paused, vm_power_state.Suspended, vm_power_state.Halted, vm_power_state.unknown}; foreach (vm_power_state ps in power_state_order) { foreach (MemSettings settings in listSettings) { if (settings.power_state == ps) { List rowVMs = settingsToVMs[settings]; bool expand = Helpers.ListsIntersect(expandedVMs, rowVMs); // expand header if any of its VMs were expanded before AddRowToPanel(new VMMemoryRow(rowVMs, expand), ref top); } } } // Remove old controls foreach (Control c in oldControls) { pageContainerPanel.Controls.Remove(c); int scroll = initScroll; if (scroll > pageContainerPanel.VerticalScroll.Maximum) scroll = pageContainerPanel.VerticalScroll.Maximum; pageContainerPanel.VerticalScroll.Value = scroll; // Without this, the scroll bar can jump around while the page is being rebuilt c.Dispose(); } _rebuilding = false; pageContainerPanel.ResumeLayout(); ReLayout(); } private void ReLayout() { Program.AssertOnEventThread(); if (_rebuilding) return; int initScroll = pageContainerPanel.VerticalScroll.Value; int top = pageContainerPanel.Padding.Top - initScroll; foreach (Control row in pageContainerPanel.Controls) { row.Top = top; top += row.Height + ROW_GAP; } } void row_Resize(object sender, EventArgs e) { ReLayout(); } private void AddRowToPanel(UserControl row, ref int top) { row.Top = top; row.Left = pageContainerPanel.Padding.Left - pageContainerPanel.HorizontalScroll.Value; SetRowWidth(row); row.Anchor = AnchorStyles.Top | AnchorStyles.Left; top += row.Height + ROW_GAP; row.Resize += row_Resize; pageContainerPanel.Controls.Add(row); } private struct MemSettings { public readonly bool has_ballooning; public readonly vm_power_state power_state; public readonly long static_min, static_max, dynamic_min, dynamic_max; public MemSettings(bool has_ballooning, vm_power_state power_state, long static_min, long static_max, long dynamic_min, long dynamic_max) { this.has_ballooning = has_ballooning; this.power_state = power_state; this.static_min = static_min; this.static_max = static_max; this.dynamic_min = dynamic_min; this.dynamic_max = dynamic_max; } } private void pageContainerPanel_SizeChanged(object sender, EventArgs e) { foreach (Control row in pageContainerPanel.Controls) SetRowWidth(row); } private void SetRowWidth(Control row) { row.Width = pageContainerPanel.Width - pageContainerPanel.Padding.Left - 25; // It won't drop below row.MinimumSize.Width though } } }