#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:
Denny Sheirer
2024-02-04 09:59:53 -05:00
committed by GitHub
parent 3acaea0019
commit 7b9adc1da2
12 changed files with 501 additions and 73 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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();
}
/**

View File

@@ -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();

View File

@@ -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!");
}
}

View File

@@ -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();
}
}

View File

@@ -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));
}
/**