User Agent Stats

pull/8/head
Qstick 3 years ago
parent 2f702ba06d
commit 0b45929ccc

@ -1,6 +1,7 @@
import Chart from 'chart.js';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import colors from 'Styles/Variables/colors';
class BarChart extends Component {
constructor(props) {
@ -10,7 +11,7 @@ class BarChart extends Component {
componentDidMount() {
this.myChart = new Chart(this.canvasRef.current, {
type: 'bar',
type: this.props.horizontal ? 'horizontalBar' : 'bar',
options: {
maintainAspectRatio: false
},
@ -18,7 +19,8 @@ class BarChart extends Component {
labels: this.props.data.map((d) => d.label),
datasets: [{
label: this.props.title,
data: this.props.data.map((d) => d.value)
data: this.props.data.map((d) => d.value),
backgroundColor: colors.chartColors
}]
}
});
@ -39,11 +41,13 @@ class BarChart extends Component {
BarChart.propTypes = {
data: PropTypes.arrayOf(PropTypes.object).isRequired,
horizontal: PropTypes.bool,
title: PropTypes.string.isRequired
};
BarChart.defaultProps = {
data: [],
horizontal: false,
title: ''
};

@ -1,6 +1,7 @@
import Chart from 'chart.js';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import colors from 'Styles/Variables/colors';
class DoughnutChart extends Component {
constructor(props) {
@ -25,7 +26,8 @@ class DoughnutChart extends Component {
labels: this.props.data.map((d) => d.label),
datasets: [{
label: this.props.title,
data: this.props.data.map((d) => d.value)
data: this.props.data.map((d) => d.value),
backgroundColor: colors.chartColors
}]
}
});

@ -4,6 +4,7 @@ import BarChart from 'Components/Chart/BarChart';
import DoughnutChart from 'Components/Chart/DoughnutChart';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import styles from './Stats.css';
@ -45,60 +46,106 @@ function getNumberGrabsData(indexerStats) {
return data;
}
function getUserAgentGrabsData(indexerStats) {
const data = indexerStats.map((indexer) => {
return {
label: indexer.userAgent ? indexer.userAgent : 'Other',
value: indexer.numberOfGrabs
};
});
data.sort((a, b) => {
return b.value - a.value;
});
return data;
}
function getUserAgentQueryData(indexerStats) {
const data = indexerStats.map((indexer) => {
return {
label: indexer.userAgent ? indexer.userAgent : 'Other',
value: indexer.numberOfQueries
};
});
data.sort((a, b) => {
return b.value - a.value;
});
return data;
}
function Stats(props) {
const {
items,
item,
isFetching,
isPopulated,
error
} = props;
const isLoaded = !!(!error && isPopulated && items.length);
const isLoaded = !!(!error && isPopulated);
return (
<PageContent>
<PageToolbar />
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div className={styles.errorMessage}>
{getErrorMessage(error, 'Failed to load indexer stats from API')}
</div>
}
{
isLoaded &&
<div>
<div className={styles.fullWidthChart}>
<BarChart
data={getAverageResponseTimeData(items)}
title='Average Response Times (ms)'
/>
</div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getTotalRequestsData(items)}
title='Total Indexer Queries'
/>
<PageContentBody>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div className={styles.errorMessage}>
{getErrorMessage(error, 'Failed to load indexer stats from API')}
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getNumberGrabsData(items)}
title='Total Indexer Grabs'
/>
}
{
isLoaded &&
<div>
<div className={styles.fullWidthChart}>
<BarChart
data={getAverageResponseTimeData(item.indexers)}
title='Average Response Times (ms)'
/>
</div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getTotalRequestsData(item.indexers)}
title='Total Indexer Queries'
/>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getNumberGrabsData(item.indexers)}
title='Total Indexer Grabs'
/>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getUserAgentQueryData(item.userAgents)}
title='Total User Agent Queries'
horizontal={true}
/>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getUserAgentGrabsData(item.userAgents)}
title='Total User Agent Grabs'
horizontal={true}
/>
</div>
</div>
</div>
}
}
</PageContentBody>
</PageContent>
);
}
Stats.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
item: PropTypes.object.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,

@ -14,13 +14,13 @@ export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: [],
item: {},
details: {
isFetching: false,
isPopulated: false,
error: null,
items: []
item: []
}
};

@ -181,5 +181,10 @@ module.exports = {
//
// Table
tableRowHoverBackgroundColor: '#fafbfc'
tableRowHoverBackgroundColor: '#fafbfc',
//
// Charts
chartColors: ['#f4d166', '#f6c760', '#f8bc58', '#f8b252', '#f7a84a', '#f69e41', '#f49538', '#f38b2f', '#f28026', '#f0751e', '#eb6c1c', '#e4641e', '#de5d1f', '#d75521', '#cf4f22', '#c64a22', '#bc4623', '#b24223', '#a83e24', '#9e3a26']
};

@ -10,4 +10,18 @@ namespace NzbDrone.Core.IndexerStats
public int NumberOfQueries { get; set; }
public int NumberOfGrabs { get; set; }
}
public class UserAgentStatistics : ResultSet
{
public string UserAgent { get; set; }
public int NumberOfQueries { get; set; }
public int NumberOfGrabs { get; set; }
}
public class HostStatistics : ResultSet
{
public string Host { get; set; }
public int NumberOfQueries { get; set; }
public int NumberOfGrabs { get; set; }
}
}

@ -10,6 +10,7 @@ namespace NzbDrone.Core.IndexerStats
public interface IIndexerStatisticsRepository
{
List<IndexerStatistics> IndexerStatistics();
List<UserAgentStatistics> UserAgentStatistics();
}
public class IndexerStatisticsRepository : IIndexerStatisticsRepository
@ -26,13 +27,13 @@ namespace NzbDrone.Core.IndexerStats
public List<IndexerStatistics> IndexerStatistics()
{
var time = DateTime.UtcNow;
return Query(Builder());
return Query(IndexerBuilder());
}
public List<IndexerStatistics> IndexerStatistics(int indexerId)
public List<UserAgentStatistics> UserAgentStatistics()
{
var time = DateTime.UtcNow;
return Query(Builder().Where<IndexerDefinition>(x => x.Id == indexerId));
return UserAgentQuery(UserAgentBuilder());
}
private List<IndexerStatistics> Query(SqlBuilder builder)
@ -45,7 +46,17 @@ namespace NzbDrone.Core.IndexerStats
}
}
private SqlBuilder Builder() => new SqlBuilder()
private List<UserAgentStatistics> UserAgentQuery(SqlBuilder builder)
{
var sql = builder.AddTemplate(_selectTemplate).LogQuery();
using (var conn = _database.OpenConnection())
{
return conn.Query<UserAgentStatistics>(sql.RawSql, sql.Parameters).ToList();
}
}
private SqlBuilder IndexerBuilder() => new SqlBuilder()
.Select(@"Indexers.Id AS IndexerId,
Indexers.Name AS IndexerName,
SUM(CASE WHEN EventType == 2 then 1 else 0 end) AS NumberOfQueries,
@ -53,5 +64,11 @@ namespace NzbDrone.Core.IndexerStats
AVG(json_extract(History.Data,'$.elapsedTime')) AS AverageResponseTime")
.Join<History.History, IndexerDefinition>((t, r) => t.IndexerId == r.Id)
.GroupBy<IndexerDefinition>(x => x.Id);
private SqlBuilder UserAgentBuilder() => new SqlBuilder()
.Select(@"json_extract(History.Data,'$.source') AS UserAgent,
SUM(CASE WHEN EventType == 2 then 1 else 0 end) AS NumberOfQueries,
SUM(CASE WHEN EventType == 1 then 1 else 0 end) AS NumberOfGrabs")
.GroupBy("UserAgent");
}
}

@ -6,6 +6,7 @@ namespace NzbDrone.Core.IndexerStats
public interface IIndexerStatisticsService
{
List<IndexerStatistics> IndexerStatistics();
List<UserAgentStatistics> UserAgentStatistics();
}
public class IndexerStatisticsService : IIndexerStatisticsService
@ -23,5 +24,12 @@ namespace NzbDrone.Core.IndexerStats
return seasonStatistics.ToList();
}
public List<UserAgentStatistics> UserAgentStatistics()
{
var seasonStatistics = _indexerStatisticsRepository.UserAgentStatistics();
return seasonStatistics.ToList();
}
}
}

@ -16,12 +16,21 @@ namespace Prowlarr.Api.V1.Indexers
{
_indexerStatisticsService = indexerStatisticsService;
GetResourceAll = GetAll;
Get("/", x =>
{
return GetAll();
});
}
private List<IndexerStatsResource> GetAll()
private IndexerStatsResource GetAll()
{
return _indexerStatisticsService.IndexerStatistics().ToResource();
var indexerResource = new IndexerStatsResource
{
Indexers = _indexerStatisticsService.IndexerStatistics(),
UserAgents = _indexerStatisticsService.UserAgentStatistics(),
};
return indexerResource;
}
}
}

@ -7,35 +7,7 @@ namespace Prowlarr.Api.V1.Indexers
{
public class IndexerStatsResource : RestResource
{
public int IndexerId { get; set; }
public string IndexerName { get; set; }
public int NumberOfQueries { get; set; }
public int AverageResponseTime { get; set; }
public int NumberOfGrabs { get; set; }
}
public static class IndexerStatsResourceMapper
{
public static IndexerStatsResource ToResource(this IndexerStatistics model)
{
if (model == null)
{
return null;
}
return new IndexerStatsResource
{
IndexerId = model.IndexerId,
IndexerName = model.IndexerName,
NumberOfQueries = model.NumberOfQueries,
AverageResponseTime = model.AverageResponseTime,
NumberOfGrabs = model.NumberOfGrabs
};
}
public static List<IndexerStatsResource> ToResource(this IEnumerable<IndexerStatistics> models)
{
return models.Select(ToResource).ToList();
}
public List<IndexerStatistics> Indexers { get; set; }
public List<UserAgentStatistics> UserAgents { get; set; }
}
}

Loading…
Cancel
Save