/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2009], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.measurement.agent.server; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Set; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.hq.agent.server.AgentStorageException; import org.hyperic.hq.agent.server.AgentStorageProvider; import org.hyperic.hq.appdef.shared.AppdefEntityID; import org.hyperic.hq.measurement.agent.ScheduledMeasurement; import org.hyperic.hq.measurement.server.session.SRN; import org.hyperic.util.encoding.Base64; /** * Class which does the storage/retrieval of schedule information from * the Agent's simple storage provider. */ class MeasurementSchedule { private static final String PROP_MSCHED = "measurement_schedule"; private static final String PROP_MSRNS = "measurement_srn"; private static final String PROP_MSRNS_LENGTH = "measurement_srn_length"; private static final int MAX_ELEM_SIZE = 10000; private final AgentStorageProvider store; private final ArrayList<SRN> srnList = new ArrayList<SRN>(); private final Log log = LogFactory.getLog(MeasurementSchedule.class); MeasurementSchedule(AgentStorageProvider store, Properties bootProps) { String info = bootProps.getProperty(PROP_MSCHED); if (info != null) { store.addOverloadedInfo(PROP_MSCHED, info); } this.store = store; this.populateSRNInfo(); } private void populateSRNInfo(){ srnList.clear(); final String lengthBuf = store.getValue(PROP_MSRNS_LENGTH); final List<Byte> encSRNBytes = new ArrayList<Byte>(); if (lengthBuf == null) { final String mSchedBuf = store.getValue(PROP_MSRNS); if (mSchedBuf == null) { log.warn("no srns to retrieve from storage"); return; } final byte[] bytes = Base64.decode(mSchedBuf); encSRNBytes.addAll(Arrays.asList(ArrayUtils.toObject(bytes))); } else { final int length = Integer.parseInt(lengthBuf); for (int i=0; i<length; i++) { final byte[] bytes = Base64.decode(store.getValue(PROP_MSRNS + "_" + i)); encSRNBytes.addAll(Arrays.asList(ArrayUtils.toObject(bytes))); } } byte[] srnBytes = ArrayUtils.toPrimitive(encSRNBytes.toArray(new Byte[0])); HashSet<AppdefEntityID> seenEnts = new HashSet<AppdefEntityID>(); String srnBuf = new String(srnBytes); ByteArrayInputStream bIs = new ByteArrayInputStream(srnBytes); DataInputStream dIs = new DataInputStream(bIs); try { int numSRNs = dIs.readInt(); int entType, entID, revNo; for (int i=0; i<numSRNs; i++) { entType = dIs.readInt(); entID = dIs.readInt(); revNo = dIs.readInt(); AppdefEntityID ent = new AppdefEntityID(entType, entID); if(seenEnts.contains(ent)){ log.warn("Entity '" + ent + "' contained more than once in SRN storage. Ignoring"); continue; } seenEnts.add(ent); srnList.add(new SRN(ent, revNo)); } } catch(IOException exc){ this.log.error("Unable to decode SRN list: " + exc + " srn=\"" + srnBuf + "\"", exc); } } private void writeSRNs() throws AgentStorageException { ByteArrayOutputStream bOs; DataOutputStream dOs; bOs = new ByteArrayOutputStream(); dOs = new DataOutputStream(bOs); synchronized(srnList){ try { dOs.writeInt(srnList.size()); for(SRN srn : srnList) { AppdefEntityID ent = srn.getEntity(); dOs.writeInt(ent.getType()); dOs.writeInt(ent.getID()); dOs.writeInt(srn.getRevisionNumber()); } List<Byte> bytes = Arrays.asList(ArrayUtils.toObject(bOs.toByteArray())); int size = bytes.size(); if (size > MAX_ELEM_SIZE) { store.setValue(PROP_MSRNS_LENGTH, new Integer((size/MAX_ELEM_SIZE) + 1).toString()); int ii=0; for (int i=0; i<size; i+=MAX_ELEM_SIZE) { int start = i; int max = Math.min(i+MAX_ELEM_SIZE, size); List<Byte> subList = bytes.subList(start, max); Byte[] b = subList.toArray(new Byte[0]); store.setValue(MeasurementSchedule.PROP_MSRNS + "_" + ii++, Base64.encode(ArrayUtils.toPrimitive(b))); } } else { store.setValue(PROP_MSRNS_LENGTH, "1"); Byte[] b = bytes.toArray(new Byte[0]); store.setValue(MeasurementSchedule.PROP_MSRNS + "_0", Base64.encode(ArrayUtils.toPrimitive(b))); } } catch(IOException exc){ this.log.error("Error encoding SRN list", exc); return; } } } /** * Converts all the records (Strings) from the collection into ScheduledMeasurement objects * and returns an Iterator to a collection containing this metrics */ private Iterator<ScheduledMeasurement> createMeasurementList(Collection<String> records){ Set<ScheduledMeasurement> metrics = new HashSet<ScheduledMeasurement>(); long i = -1; for (String value : records) { i++; ScheduledMeasurement metric; if((metric = ScheduledMeasurement.decode(value)) == null){ this.log.error("Unable to decode metric from storage, deleting."); try { this.store.removeFromList(MeasurementSchedule.PROP_MSCHED, i); } catch (AgentStorageException e) { log.debug(e,e); } continue; } metrics.add(metric); } log.info("Number of metrics decoded from the storage - " + metrics.size()); return metrics.iterator(); } /** * Get a list of all the measurements within the storage. * @throws IOException */ public synchronized Iterator<ScheduledMeasurement> getMeasurementList() throws IOException { Collection<String> records = new ArrayList<String>(); try { readRecordsFromStorage(records); } catch (Exception e) { //For version 4.6.5 the default record size for Disk list was changed from 1024 to 4000 //If we get an exception here this is probably because this is the first startup after an //upgrade from a pre 4.6.5 version and we need to try to fix the list log.warn("Error reading measurement list from storage = '" + e + "' ," + " trying to convert the list records size"); store.convertListToCurrentRecordSize(MeasurementSchedule.PROP_MSCHED); //If this time readRecordsFromStorage() we don't want to catch the exception, //the AgentDeamon will catch this exception and fail the agent startup readRecordsFromStorage(records); } return createMeasurementList(records); } /** * Reads all the records from the measurement_schedule storage list and writes * them into the collection * @param records - adds all the records from the storage to this collection */ private void readRecordsFromStorage(Collection<String> records) { Iterator<String> i; records.clear(); i = store.getListIterator(MeasurementSchedule.PROP_MSCHED); for(; i != null && i.hasNext(); ){ String value = i.next(); records.add(value); } } synchronized void storeMeasurements(Collection<ScheduledMeasurement> measurements) throws AgentStorageException { for (ScheduledMeasurement m : measurements) { final String encoded = m.encode(); store.addToList(MeasurementSchedule.PROP_MSCHED, encoded.toString()); } store.flush(); } synchronized void storeMeasurement(ScheduledMeasurement newMeas) throws AgentStorageException { final String encoded = newMeas.encode(); store.addToList(MeasurementSchedule.PROP_MSCHED, encoded.toString()); store.flush(); } void updateSRN(SRN updSRN) throws AgentStorageException { AppdefEntityID ent = updSRN.getEntity(); boolean toWrite = false; boolean found = false; synchronized(this.srnList){ final boolean debug = log.isDebugEnabled(); for (SRN srn : srnList) { if(found = srn.getEntity().equals(ent)) { if (toWrite = srn.getRevisionNumber() != updSRN.getRevisionNumber()) { if (debug) { log.debug("Updating SRN for " + ent + " from " + srn.getRevisionNumber() + " to " + updSRN.getRevisionNumber()); } srn.setRevisionNumber(updSRN.getRevisionNumber()); } break; } } if(!found){ log.debug("Adding new SRN for entity " + ent + ": Initial value = " + updSRN.getRevisionNumber()); srnList.add(updSRN); toWrite = true; } if(toWrite){ writeSRNs(); } } } void removeSRN(AppdefEntityID ent) throws AgentStorageException { boolean debug = this.log.isDebugEnabled(); boolean toWrite = false, found = false; synchronized (this.srnList) { for (Iterator<SRN> i = this.srnList.iterator(); i.hasNext();) { SRN srn = i.next(); if (srn.getEntity().equals(ent)) { found = true; i.remove(); toWrite = true; break; } } if (found) { if (debug) { this.log.debug("SRN for entity " + ent + " removed"); } } else { if (debug) { this.log.debug("SRN for entity " + ent + " not found"); } } if (toWrite) { this.writeSRNs(); } } } /** * Delete measurements matching a specific client IDs. * * @param clientID Metrics matching this ID will be deleted * @param srnInfo Updated SRN information for the entity which * matches the clientID. If this value is null * (i.e. it is the last clientID for a resource, or * an update of the SRN is unnecessary), then the * SRN will not be updated. */ synchronized void deleteMeasurements(Set<AppdefEntityID> aeids) throws AgentStorageException { if (aeids == null || aeids.isEmpty()) { return; } final Iterator<String> i = store.getListIterator(MeasurementSchedule.PROP_MSCHED); while (i != null && i.hasNext()){ final String value = i.next(); final ScheduledMeasurement meas = ScheduledMeasurement.decode(value); if (null == meas) { log.error("Unable to decode metric from storage, removing metric for entity"); i.remove(); continue; } final AppdefEntityID entity = meas.getEntity(); if (aeids.contains(entity)) { log.debug("Removing scheduled measurement " + meas); i.remove(); } } // Clear out the SRN synchronized(this.srnList){ final Iterator<SRN> it = srnList.iterator(); while (it.hasNext()) { final SRN srn = it.next(); final AppdefEntityID entity = srn.getEntity(); if (aeids.contains(entity)) { it.remove(); } } writeSRNs(); } store.flush(); } SRN[] getSRNsAsArray(){ synchronized(this.srnList){ return (SRN[]) this.srnList.toArray(new SRN[0]); } } }