mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#1824 Adds diagnostic report generation to File>>Reports menu to log thread dumps and generate a processing diagnostic/status report. (#1825)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -35,11 +35,10 @@ import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier
|
||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Channel metadata containing details about the channel configuration, decoder state and current
|
||||
@@ -66,17 +65,51 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
|
||||
private AliasModel mAliasModel;
|
||||
private AliasList mAliasList;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel for alias lookups
|
||||
* @param timeslot for this metadata
|
||||
*/
|
||||
public ChannelMetadata(AliasModel aliasModel, Integer timeslot)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mTimeslot = timeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel for alias lookups
|
||||
*/
|
||||
public ChannelMetadata(AliasModel aliasModel)
|
||||
{
|
||||
this(aliasModel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a textual description of this channel metadata.
|
||||
*/
|
||||
public String getDescription()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Channel Metadata Description\n");
|
||||
sb.append("\tTimeslot: ").append(mTimeslot).append("\n");
|
||||
Identifier decoder = getDecoderTypeConfigurationIdentifier();
|
||||
sb.append("\tDecoder: ").append(decoder != null ? decoder : "(null)").append("\n");
|
||||
Identifier state = getChannelStateIdentifier();
|
||||
sb.append("\tState: ").append(state != null ? state : "(null)").append("\n");
|
||||
Identifier system = getSystemConfigurationIdentifier();
|
||||
sb.append("\tSystem: ").append(system != null ? system : "(null)").append("\n");
|
||||
Identifier site = getSiteConfigurationIdentifier();
|
||||
sb.append("\tSite: ").append(site != null ? site : "(null)").append("\n");
|
||||
Identifier channel = getChannelNameConfigurationIdentifier();
|
||||
sb.append("\tChannel: ").append(channel != null ? channel : "(null)").append("\n");
|
||||
Identifier frequency = getFrequencyConfigurationIdentifier();
|
||||
sb.append("\tFrequency: ").append(frequency != null ? frequency : "(null)").append("\n");
|
||||
Identifier logical = getDecoderLogicalChannelNameIdentifier();
|
||||
sb.append("\tLogical Channel: ").append(logical != null ? logical : "(null)").append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Integer getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.channel.metadata;
|
||||
|
||||
@@ -29,16 +26,16 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.awt.EventQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
public class ChannelMetadataModel extends AbstractTableModel implements IChannelMetadataUpdateListener
|
||||
{
|
||||
@@ -84,6 +81,47 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic description of the channel metadata and metadata-channel map.
|
||||
* @return diagnostic description.
|
||||
*/
|
||||
public String getDiagnosticInformation()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Map<ChannelMetadata,Channel> mapCopy = new HashMap<>(mMetadataChannelMap);
|
||||
List<ChannelMetadata> metadataCopy = new ArrayList<>(mChannelMetadata);
|
||||
|
||||
sb.append("Channel Metadata Model Diagnostics\n");
|
||||
|
||||
for(ChannelMetadata channelMetadata: metadataCopy)
|
||||
{
|
||||
sb.append("\n--------------- CHANNEL METADATA ITEM --------------------\n\n");
|
||||
if(mapCopy.containsKey(channelMetadata))
|
||||
{
|
||||
Channel channel = mapCopy.get(channelMetadata);
|
||||
sb.append("\tMapped Channel:").append(channel).append("\n");
|
||||
sb.append("\t\tSource Configuration: ").append(channel.getSourceConfiguration()).append("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("\tMapped Channel: **Channel Metadata Is Not In the Metadata:Channel Map**\n");
|
||||
}
|
||||
|
||||
sb.append(channelMetadata.getDescription());
|
||||
}
|
||||
|
||||
//Check for orphaned map entries that don't match the current channel metadata list.
|
||||
for(Map.Entry<ChannelMetadata,Channel> entry: mapCopy.entrySet())
|
||||
{
|
||||
if(!metadataCopy.contains(entry.getKey()))
|
||||
{
|
||||
sb.append("\n\tWARNING: orphaned Metadata Channel map entry - channel: " + entry.getValue()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.channel.metadata.ChannelAndMetadata;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadataModel;
|
||||
import io.github.dsheirer.channel.state.AbstractChannelState;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
|
||||
@@ -52,6 +53,7 @@ import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -72,9 +74,10 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
{
|
||||
private static final String DIVIDER = "-------------------------------------------------------------------------\n";
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelProcessingManager.class);
|
||||
private static final String TUNER_UNAVAILABLE_DESCRIPTION = "TUNER UNAVAILABLE";
|
||||
private Map<Channel,ProcessingChain> mProcessingChains = new ConcurrentHashMap<>();
|
||||
private Map<Channel,ProcessingChain> mProcessingChainsMap = new ConcurrentHashMap<>();
|
||||
private Lock mLock = new ReentrantLock();
|
||||
|
||||
private ChannelSourceEventErrorListener mSourceErrorListener = new ChannelSourceEventErrorListener();
|
||||
@@ -131,7 +134,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
isProcessing = mProcessingChains.containsKey(channel) && mProcessingChains.get(channel).isProcessing();
|
||||
isProcessing = mProcessingChainsMap.containsKey(channel) && mProcessingChainsMap.get(channel).isProcessing();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -147,7 +150,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*/
|
||||
public boolean isProcessing()
|
||||
{
|
||||
return !mProcessingChains.isEmpty();
|
||||
return !mProcessingChainsMap.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +159,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*/
|
||||
public ProcessingChain getProcessingChain(Channel channel)
|
||||
{
|
||||
return mProcessingChains.get(channel);
|
||||
return mProcessingChainsMap.get(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +178,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChains.entrySet())
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChainsMap.entrySet())
|
||||
{
|
||||
if(entry.getValue() == processingChain)
|
||||
{
|
||||
@@ -206,7 +209,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChains.entrySet())
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChainsMap.entrySet())
|
||||
{
|
||||
if(entry.getValue().hasSource(tunerChannelSource))
|
||||
{
|
||||
@@ -556,10 +559,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
if(!mProcessingChains.containsKey(channel))
|
||||
if(!mProcessingChainsMap.containsKey(channel))
|
||||
{
|
||||
added = true;
|
||||
mProcessingChains.put(channel, processingChain);
|
||||
mProcessingChainsMap.put(channel, processingChain);
|
||||
getChannelMetadataModel().add(new ChannelAndMetadata(channel, processingChain.getChannelState().getChannelMetadata()));
|
||||
}
|
||||
}
|
||||
@@ -584,7 +587,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
removed = mProcessingChains.remove(channel);
|
||||
removed = mProcessingChainsMap.remove(channel);
|
||||
|
||||
if(removed != null)
|
||||
{
|
||||
@@ -660,7 +663,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
mDelayedChannelStartTasks.remove(delayedTask);
|
||||
}
|
||||
|
||||
List<Channel> channelsToStop = new ArrayList<>(mProcessingChains.keySet());
|
||||
List<Channel> channelsToStop = new ArrayList<>(mProcessingChainsMap.keySet());
|
||||
|
||||
for(Channel channel : channelsToStop)
|
||||
{
|
||||
@@ -683,7 +686,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
public void convertToTrafficChannel(ChannelConversionRequest request)
|
||||
{
|
||||
//Update the channel to processing chain map.
|
||||
ProcessingChain processingChain = mProcessingChains.remove(request.getCurrentChannel());
|
||||
ProcessingChain processingChain = mProcessingChainsMap.remove(request.getCurrentChannel());
|
||||
|
||||
if(processingChain != null)
|
||||
{
|
||||
@@ -697,7 +700,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
request.getTrafficChannel().setProcessing(true);
|
||||
});
|
||||
|
||||
mProcessingChains.put(request.getTrafficChannel(), processingChain);
|
||||
mProcessingChainsMap.put(request.getTrafficChannel(), processingChain);
|
||||
mChannelMetadataModel.updateChannelMetadataToChannelMap(processingChain.getChannelState().getChannelMetadata(),
|
||||
request.getTrafficChannel());
|
||||
|
||||
@@ -803,7 +806,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChainsMap.entrySet())
|
||||
{
|
||||
if(entry.getValue().hasSource(sourceEvent.getSource()))
|
||||
{
|
||||
@@ -841,4 +844,54 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic report.
|
||||
* @return report text.
|
||||
*/
|
||||
public String getDiagnosticInformation()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Channel Processing Manager - Diagnostics Report\n\n");
|
||||
sb.append(DIVIDER);
|
||||
sb.append("\tChannel to Processing Chain Map Contents\n");
|
||||
Map<Channel,ProcessingChain> mapCopy = new HashMap<>(mProcessingChainsMap);
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mapCopy.entrySet())
|
||||
{
|
||||
sb.append("\n\n--------------- CHANNEL:PROCESSING CHAIN MAP ENTRY --------------------\n");
|
||||
try
|
||||
{
|
||||
Channel channel = entry.getKey();
|
||||
sb.append("\tChannel: " + channel).append("\n");
|
||||
sb.append("\t\tSource Configuration: " + channel.getSourceConfiguration()).append("\n");
|
||||
ProcessingChain chain = entry.getValue();
|
||||
sb.append("\tProcessing Chain - Processing: ").append(chain.isProcessing()).append("\n");
|
||||
AbstractChannelState state = chain.getChannelState();
|
||||
sb.append("Channel State: ").append(state.getClass()).append("\n");
|
||||
for(ChannelMetadata metadata: state.getChannelMetadata())
|
||||
{
|
||||
sb.append(metadata.getDescription()).append("\n");
|
||||
}
|
||||
|
||||
Source source = chain.getSource();
|
||||
|
||||
if(source != null)
|
||||
{
|
||||
sb.append("Channel Source Class: " + source.getClass()).append("\n");
|
||||
sb.append("\t\tTo String:").append(source).append("\n");
|
||||
sb.append("\t\tHash:").append(Integer.toHexString(source.hashCode()).toUpperCase()).append("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Channel Source: (null)\n");
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
sb.append("\tError while logging diagnostics of map entry - " + t.getMessage()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -147,6 +147,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
|
||||
sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
|
||||
sb.append(" | Polyphase Indices: ").append(indexes);
|
||||
sb.append(" HASH:").append(Integer.toHexString(pcs.hashCode()).toUpperCase());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -43,6 +43,7 @@ import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.log.ApplicationLog;
|
||||
import io.github.dsheirer.map.MapService;
|
||||
import io.github.dsheirer.module.log.EventLogManager;
|
||||
import io.github.dsheirer.monitor.DiagnosticMonitor;
|
||||
import io.github.dsheirer.monitor.ResourceMonitor;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
@@ -125,6 +126,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
private AudioStreamingManager mAudioStreamingManager;
|
||||
private BroadcastStatusPanel mBroadcastStatusPanel;
|
||||
private ControllerPanel mControllerPanel;
|
||||
private DiagnosticMonitor mDiagnosticMonitor;
|
||||
private IconModel mIconModel = new IconModel();
|
||||
private PlaylistManager mPlaylistManager;
|
||||
private SettingsManager mSettingsManager;
|
||||
@@ -187,6 +189,9 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences);
|
||||
mPlaylistManager = new PlaylistManager(mUserPreferences, mTunerManager, aliasModel, eventLogManager, mIconModel);
|
||||
|
||||
mDiagnosticMonitor = new DiagnosticMonitor(mUserPreferences, mPlaylistManager.getChannelProcessingManager(),
|
||||
mTunerManager);
|
||||
|
||||
if(!GraphicsEnvironment.isHeadless())
|
||||
{
|
||||
mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences, mTunerManager, mPlaylistManager);
|
||||
@@ -411,6 +416,46 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
menuBar.add(fileMenu);
|
||||
|
||||
JMenuItem processingStatusReportMenuItem = new JMenuItem("Processing Diagnostic Report");
|
||||
processingStatusReportMenuItem.addActionListener(e -> {
|
||||
try
|
||||
{
|
||||
Path path = mDiagnosticMonitor.generateProcessingDiagnosticReport();
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui, "Report created: " +
|
||||
path.toString(), "Processing Status Report Created", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error creating processing status report file", ioe);
|
||||
JOptionPane.showMessageDialog(mMainGui, "Unable to create report file. Please " +
|
||||
"see application log for details.", "Processing Status Report Failed", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem threadDumpReportMenuItem = new JMenuItem("Thread Dump Report");
|
||||
threadDumpReportMenuItem.addActionListener(e -> {
|
||||
try
|
||||
{
|
||||
Path path = mDiagnosticMonitor.generateThreadDumpReport();
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui, "Report created: " +
|
||||
path.toString(), "Thread Dump Report Created", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error creating thread dump report file", ioe);
|
||||
JOptionPane.showMessageDialog(mMainGui, "Unable to create report file. Please " +
|
||||
"see application log for details.", "Thread Dump Report Failed", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
JMenu diagnosticMenu = new JMenu(("Reports"));
|
||||
diagnosticMenu.add(processingStatusReportMenuItem);
|
||||
diagnosticMenu.add(threadDumpReportMenuItem);
|
||||
fileMenu.add(diagnosticMenu);
|
||||
fileMenu.add(new JSeparator(JSeparator.HORIZONTAL));
|
||||
|
||||
JMenuItem exitMenu = new JMenuItem("Exit");
|
||||
exitMenu.addActionListener(event -> {
|
||||
processShutdown();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.log;
|
||||
@@ -30,16 +29,15 @@ import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Logback and SLF4j logging implementation.
|
||||
@@ -182,7 +180,11 @@ public class ApplicationLog
|
||||
}
|
||||
}
|
||||
|
||||
private Attributes findManifestAttributes() {
|
||||
/**
|
||||
* Finds the jar manifest attributes
|
||||
* @return attributes or null.
|
||||
*/
|
||||
public Attributes findManifestAttributes() {
|
||||
try {
|
||||
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
@@ -197,9 +199,12 @@ public class ApplicationLog
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
193
src/main/java/io/github/dsheirer/monitor/DiagnosticMonitor.java
Normal file
193
src/main/java/io/github/dsheirer/monitor/DiagnosticMonitor.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.monitor;
|
||||
|
||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.TimeStamp;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Utility class for monitoring system components and producing logging reports.
|
||||
*/
|
||||
public class DiagnosticMonitor
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DiagnosticMonitor.class);
|
||||
private static final String DIVIDER = "\n\n=========================================================================\n\n";
|
||||
private UserPreferences mUserPreferences;
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
private TunerManager mTunerManager;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for application logging directory lookup.
|
||||
*/
|
||||
public DiagnosticMonitor(UserPreferences userPreferences, ChannelProcessingManager channelProcessingManager,
|
||||
TunerManager tunerManager)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
mChannelProcessingManager = channelProcessingManager;
|
||||
mTunerManager = tunerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic report containing state information for channels that are in a processing state.
|
||||
* @return path for the log file that was created.
|
||||
*/
|
||||
public Path generateProcessingDiagnosticReport() throws IOException
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("sdrtrunk Processing Diagnostic Report\n");
|
||||
sb.append(DIVIDER);
|
||||
sb.append(getEnvironmentReport());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(mTunerManager.getDiscoveredTunerModel().getDiagnosticReport());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(mChannelProcessingManager.getDiagnosticInformation());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(mChannelProcessingManager.getChannelMetadataModel().getDiagnosticInformation());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(getThreadDumpReport());
|
||||
sb.append(DIVIDER);
|
||||
|
||||
Path logDirectory = mUserPreferences.getDirectoryPreference().getDirectoryApplicationLog();
|
||||
String file = TimeStamp.getFileFormattedDateTime() + "_sdrtrunk_processing_diagnostic_report.log";
|
||||
Path output = logDirectory.resolve(file);
|
||||
Files.write(output, sb.toString().getBytes());
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the current threads to a log file with current date and time to the application log directory.
|
||||
* @return path to the thread dump log file that was created.
|
||||
* @throws IOException if there is an issue writing the contents to the log file.
|
||||
*/
|
||||
public Path generateThreadDumpReport() throws IOException
|
||||
{
|
||||
String report = getThreadDumpReport();
|
||||
Path logDirectory = mUserPreferences.getDirectoryPreference().getDirectoryApplicationLog();
|
||||
String file = TimeStamp.getFileFormattedDateTime() + "_sdrtrunk_thread_dump.log";
|
||||
Path output = logDirectory.resolve(file);
|
||||
Files.write(output, report.getBytes());
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a thread dump report.
|
||||
* @return report text.
|
||||
*/
|
||||
public String getThreadDumpReport()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Thread Dump Report\n\n");
|
||||
|
||||
for(Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet())
|
||||
{
|
||||
sb.append(entry.getKey() + " " + entry.getKey().getState()).append("\n");
|
||||
|
||||
for(StackTraceElement ste : entry.getValue())
|
||||
{
|
||||
sb.append("\tat " + ste).append("\n");
|
||||
}
|
||||
|
||||
sb.append("\n\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a JVM and application environment report
|
||||
*/
|
||||
public String getEnvironmentReport()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("JVM and Application Environment Report\n");
|
||||
Attributes atts = findManifestAttributes();
|
||||
if (atts != null) {
|
||||
sb.append("\nVersion : " + atts.getValue("Implementation-Version"));
|
||||
sb.append("\nGradle Version : " + atts.getValue("Created-By"));
|
||||
sb.append("\nBuild Timestamp : " + atts.getValue("Build-Timestamp"));
|
||||
sb.append("\nBuild-JDK : " + atts.getValue("Build-JDK"));
|
||||
sb.append("\nBuild OS : " + atts.getValue("Build-OS"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("\nApplication: no build information available");
|
||||
}
|
||||
|
||||
sb.append("\nHost OS Name: " + System.getProperty("os.name"));
|
||||
sb.append("\nHost OS Arch: " + System.getProperty("os.arch"));
|
||||
sb.append("\nHost OS Version: " + System.getProperty("os.version"));
|
||||
sb.append("\nHost CPU Cores: " + Runtime.getRuntime().availableProcessors());
|
||||
sb.append("\nHost Max Java Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
|
||||
sb.append("\nHost Allocated Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().totalMemory()));
|
||||
sb.append("\nHost Free Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().freeMemory()));
|
||||
sb.append("\nHost Used Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().totalMemory() -
|
||||
Runtime.getRuntime().freeMemory()));
|
||||
sb.append("\nStorage Directories:");
|
||||
sb.append("\n Application Root: " + mUserPreferences.getDirectoryPreference().getDirectoryApplicationRoot());
|
||||
sb.append("\n Application Log: " + mUserPreferences.getDirectoryPreference().getDirectoryApplicationLog());
|
||||
sb.append("\n Event Log: " + mUserPreferences.getDirectoryPreference().getDirectoryEventLog());
|
||||
sb.append("\n Playlist: " + mUserPreferences.getDirectoryPreference().getDirectoryPlaylist());
|
||||
sb.append("\n Recordings: " + mUserPreferences.getDirectoryPreference().getDirectoryRecording());
|
||||
sb.append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the jar manifest attributes
|
||||
* @return attributes or null.
|
||||
*/
|
||||
public Attributes findManifestAttributes() {
|
||||
try {
|
||||
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
try {
|
||||
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
||||
Attributes atts = manifest.getMainAttributes();
|
||||
Boolean hasTitle = atts.containsValue("sdrtrunk project");
|
||||
if (hasTitle) {
|
||||
return atts;
|
||||
}
|
||||
} catch (IOException E) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -59,13 +59,27 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
|
||||
* Logs current state of the tuner
|
||||
*/
|
||||
public void logState()
|
||||
{
|
||||
mLog.info(getDiagnosticReport());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a state report for this tuner.
|
||||
* @return
|
||||
*/
|
||||
public String getDiagnosticReport()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Discovered Tuner: ").append(getId());
|
||||
sb.append("\n\tClass:").append(getClass());
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
sb.append("\n\tTuner - Frequency:").append(getTuner().getTunerController().getFrequency());
|
||||
sb.append("\n\tTuner Class:").append(getTuner().getClass());
|
||||
sb.append("\n\tTuner Controller Class:").append(getTuner().getTunerController().getClass());
|
||||
sb.append("\n\tFrequency:").append(getTuner().getTunerController().getFrequency());
|
||||
sb.append("\n\tError:").append(getErrorMessage());
|
||||
sb.append("\n\tChannel Manager Class:").append(getTuner().getChannelSourceManager().getClass());
|
||||
sb.append("\n\tChannel Manager:").append(getTuner().getChannelSourceManager().getStateDescription());
|
||||
}
|
||||
else
|
||||
@@ -73,7 +87,7 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
|
||||
sb.append("\n\tTuner - no tuner");
|
||||
}
|
||||
|
||||
mLog.info(sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -72,6 +72,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
.append("] Frequency [").append(channelSource.getFrequency())
|
||||
.append("] Mixer [").append(channelSource.getMixerFrequency())
|
||||
.append("]");
|
||||
sb.append(" HASH:").append(Integer.toHexString(channelSource.hashCode()).toUpperCase());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -776,16 +776,4 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.info("Starting ...");
|
||||
UserPreferences userPreferences = new UserPreferences();
|
||||
TunerManager tunerManager = new TunerManager(userPreferences);
|
||||
tunerManager.start();
|
||||
|
||||
while(true);
|
||||
|
||||
// mLog.info("Finished!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -526,4 +526,23 @@ public class DiscoveredTunerModel extends AbstractTableModel implements Listener
|
||||
{
|
||||
return COLUMN_HEADERS[columnIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a diagnostic report for all discovered tuners.
|
||||
*/
|
||||
public String getDiagnosticReport()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Discovered Tuner Model Diagnostic Report\n");
|
||||
|
||||
List<DiscoveredTuner> tunersCopy = new ArrayList<>(mDiscoveredTuners);
|
||||
|
||||
for(DiscoveredTuner tuner: tunersCopy)
|
||||
{
|
||||
sb.append("\n\n--------------- DISCOVERED TUNER --------------------\n\n");
|
||||
sb.append(tuner.getDiagnosticReport()).append("\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* 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
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.util;
|
||||
|
||||
@@ -26,8 +25,9 @@ public class TimeStamp
|
||||
{
|
||||
public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
|
||||
public static SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HHmmss");
|
||||
public static SimpleDateFormat DATE_WITH_MILLISECONDS_FORMAT = new SimpleDateFormat("HHmmss.SSS");
|
||||
public static SimpleDateFormat TIME_WITH_MILLISECONDS_FORMAT = new SimpleDateFormat("HHmmss.SSS");
|
||||
public static SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
|
||||
public static SimpleDateFormat DATE_TIME_FORMAT_FILE = new SimpleDateFormat("yyyyMMdd_HHmmss");
|
||||
public static SimpleDateFormat DATE_TIME_MILLIS_FORMAT = new SimpleDateFormat("yyyyMMdd HHmmss.SSS");
|
||||
|
||||
/**
|
||||
@@ -46,6 +46,44 @@ public class TimeStamp
|
||||
return DATE_FORMAT.format(new Date(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Date time formatted for use in a file.
|
||||
* @param timestamp to format
|
||||
* @return format string
|
||||
*/
|
||||
public static String getFileFormattedDateTime(long timestamp)
|
||||
{
|
||||
return DATE_TIME_FORMAT_FILE.format(new Date(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Current date and time formatted for use in a file.
|
||||
* @return format string
|
||||
*/
|
||||
public static String getFileFormattedDateTime()
|
||||
{
|
||||
return getFileFormattedDateTime(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates formatted date and timestamp
|
||||
* @param timestamp to format
|
||||
* @return formatted date and time
|
||||
*/
|
||||
public static String getFormattedDateTime(long timestamp)
|
||||
{
|
||||
return DATE_TIME_FORMAT.format(new Date(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates formatted date and timestamp using now as the timestamp.
|
||||
* @return formatted date and time
|
||||
*/
|
||||
public static String getFormattedDateTime()
|
||||
{
|
||||
return getFormattedDateTime(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current system time formatted as HH:mm:ss
|
||||
*/
|
||||
@@ -67,7 +105,7 @@ public class TimeStamp
|
||||
*/
|
||||
public static synchronized String getFormattedTimeWithMilliseconds(long timestamp)
|
||||
{
|
||||
return DATE_WITH_MILLISECONDS_FORMAT.format(new Date(timestamp));
|
||||
return TIME_WITH_MILLISECONDS_FORMAT.format(new Date(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user