Add redundancy stats
This commit is contained in:
		
							parent
							
								
									cfc16a6db8
								
							
						
					
					
						commit
						4b5384f6e7
					
				
					 8 changed files with 132 additions and 33 deletions
				
			
		| 
						 | 
				
			
			@ -21,6 +21,16 @@ export { overviewsRouter }
 | 
			
		|||
 | 
			
		||||
// ---------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
const buildSamples = memoizee(async function () {
 | 
			
		||||
  const [ categories, channels, tags ] = await Promise.all([
 | 
			
		||||
    VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
 | 
			
		||||
    VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD ,OVERVIEWS.VIDEOS.SAMPLES_COUNT),
 | 
			
		||||
    TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT)
 | 
			
		||||
  ])
 | 
			
		||||
 | 
			
		||||
  return { categories, channels, tags }
 | 
			
		||||
}, { maxAge: MEMOIZE_TTL.OVERVIEWS_SAMPLE })
 | 
			
		||||
 | 
			
		||||
// This endpoint could be quite long, but we cache it
 | 
			
		||||
async function getVideosOverview (req: express.Request, res: express.Response) {
 | 
			
		||||
  const attributes = await buildSamples()
 | 
			
		||||
| 
						 | 
				
			
			@ -45,16 +55,6 @@ async function getVideosOverview (req: express.Request, res: express.Response) {
 | 
			
		|||
  return res.json(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const buildSamples = memoizee(async function () {
 | 
			
		||||
  const [ categories, channels, tags ] = await Promise.all([
 | 
			
		||||
    VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
 | 
			
		||||
    VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD ,OVERVIEWS.VIDEOS.SAMPLES_COUNT),
 | 
			
		||||
    TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT)
 | 
			
		||||
  ])
 | 
			
		||||
 | 
			
		||||
  return { categories, channels, tags }
 | 
			
		||||
}, { maxAge: MEMOIZE_TTL.OVERVIEWS_SAMPLE })
 | 
			
		||||
 | 
			
		||||
async function getVideosByTag (tag: string, res: express.Response) {
 | 
			
		||||
  const videos = await getVideos(res, { tagsOneOf: [ tag ] })
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,10 +5,14 @@ import { UserModel } from '../../../models/account/user'
 | 
			
		|||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 | 
			
		||||
import { VideoModel } from '../../../models/video/video'
 | 
			
		||||
import { VideoCommentModel } from '../../../models/video/video-comment'
 | 
			
		||||
import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
 | 
			
		||||
import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
 | 
			
		||||
import { cacheRoute } from '../../../middlewares/cache'
 | 
			
		||||
 | 
			
		||||
const statsRouter = express.Router()
 | 
			
		||||
 | 
			
		||||
statsRouter.get('/stats',
 | 
			
		||||
  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.STATS)),
 | 
			
		||||
  asyncMiddleware(getStats)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +22,13 @@ async function getStats (req: express.Request, res: express.Response, next: expr
 | 
			
		|||
  const { totalUsers } = await UserModel.getStats()
 | 
			
		||||
  const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
 | 
			
		||||
 | 
			
		||||
  const videosRedundancyStats = await Promise.all(
 | 
			
		||||
    CONFIG.REDUNDANCY.VIDEOS.map(r => {
 | 
			
		||||
      return VideoRedundancyModel.getStats(r.strategy)
 | 
			
		||||
        .then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size }))
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const data: ServerStats = {
 | 
			
		||||
    totalLocalVideos,
 | 
			
		||||
    totalLocalVideoViews,
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +37,8 @@ async function getStats (req: express.Request, res: express.Response, next: expr
 | 
			
		|||
    totalVideoComments,
 | 
			
		||||
    totalUsers,
 | 
			
		||||
    totalInstanceFollowers,
 | 
			
		||||
    totalInstanceFollowing
 | 
			
		||||
    totalInstanceFollowing,
 | 
			
		||||
    videosRedundancy: videosRedundancyStats
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return res.json(data).end()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,7 +66,8 @@ const ROUTE_CACHE_LIFETIME = {
 | 
			
		|||
  },
 | 
			
		||||
  ACTIVITY_PUB: {
 | 
			
		||||
    VIDEOS: '1 second' // 1 second, cache concurrent requests after a broadcast for example
 | 
			
		||||
  }
 | 
			
		||||
  },
 | 
			
		||||
  STATS: '4 hours'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ---------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -245,6 +245,37 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
 | 
			
		|||
                               .findAll(query)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async getStats (strategy: VideoRedundancyStrategy) {
 | 
			
		||||
    const actor = await getServerActor()
 | 
			
		||||
 | 
			
		||||
    const query = {
 | 
			
		||||
      raw: true,
 | 
			
		||||
      attributes: [
 | 
			
		||||
        [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ],
 | 
			
		||||
        [ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', 'videoId')), 'totalVideos' ],
 | 
			
		||||
        [ Sequelize.fn('COUNT', 'videoFileId'), 'totalVideoFiles' ]
 | 
			
		||||
      ],
 | 
			
		||||
      where: {
 | 
			
		||||
        strategy,
 | 
			
		||||
        actorId: actor.id
 | 
			
		||||
      },
 | 
			
		||||
      include: [
 | 
			
		||||
        {
 | 
			
		||||
          attributes: [],
 | 
			
		||||
          model: VideoFileModel,
 | 
			
		||||
          required: true
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return VideoRedundancyModel.find(query as any) // FIXME: typings
 | 
			
		||||
      .then((r: any) => ({
 | 
			
		||||
        totalUsed: parseInt(r.totalUsed.toString(), 10),
 | 
			
		||||
        totalVideos: r.totalVideos,
 | 
			
		||||
        totalVideoFiles: r.totalVideoFiles
 | 
			
		||||
      }))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toActivityPubObject (): CacheFileObject {
 | 
			
		||||
    return {
 | 
			
		||||
      id: this.url,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,8 @@ import { ActorFollow } from '../../../../shared/models/actors'
 | 
			
		|||
import { readdir } from 'fs-extra'
 | 
			
		||||
import { join } from 'path'
 | 
			
		||||
import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy'
 | 
			
		||||
import { getStats } from '../../utils/server/stats'
 | 
			
		||||
import { ServerStats } from '../../../../shared/models/server/server-stats.model'
 | 
			
		||||
 | 
			
		||||
const expect = chai.expect
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -79,16 +81,32 @@ async function runServers (strategy: VideoRedundancyStrategy, additionalParams:
 | 
			
		|||
  await waitJobs(servers)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function check1WebSeed () {
 | 
			
		||||
async function check1WebSeed (strategy: VideoRedundancyStrategy) {
 | 
			
		||||
  const webseeds = [
 | 
			
		||||
    'http://localhost:9002/static/webseed/' + video1Server2UUID
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  for (const server of servers) {
 | 
			
		||||
    const res = await getVideo(server.url, video1Server2UUID)
 | 
			
		||||
    {
 | 
			
		||||
      const res = await getVideo(server.url, video1Server2UUID)
 | 
			
		||||
 | 
			
		||||
    const video: VideoDetails = res.body
 | 
			
		||||
    video.files.forEach(f => checkMagnetWebseeds(f, webseeds))
 | 
			
		||||
      const video: VideoDetails = res.body
 | 
			
		||||
      video.files.forEach(f => checkMagnetWebseeds(f, webseeds))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
      const res = await getStats(server.url)
 | 
			
		||||
      const data: ServerStats = res.body
 | 
			
		||||
 | 
			
		||||
      expect(data.videosRedundancy).to.have.lengthOf(1)
 | 
			
		||||
 | 
			
		||||
      const stat = data.videosRedundancy[0]
 | 
			
		||||
      expect(stat.strategy).to.equal(strategy)
 | 
			
		||||
      expect(stat.totalSize).to.equal(102400)
 | 
			
		||||
      expect(stat.totalUsed).to.equal(0)
 | 
			
		||||
      expect(stat.totalVideoFiles).to.equal(0)
 | 
			
		||||
      expect(stat.totalVideos).to.equal(0)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +125,7 @@ async function enableRedundancy () {
 | 
			
		|||
  expect(server2.following.hostRedundancyAllowed).to.be.true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function check2Webseeds () {
 | 
			
		||||
async function check2Webseeds (strategy: VideoRedundancyStrategy) {
 | 
			
		||||
  await waitJobs(servers)
 | 
			
		||||
  await wait(15000)
 | 
			
		||||
  await waitJobs(servers)
 | 
			
		||||
| 
						 | 
				
			
			@ -118,12 +136,14 @@ async function check2Webseeds () {
 | 
			
		|||
  ]
 | 
			
		||||
 | 
			
		||||
  for (const server of servers) {
 | 
			
		||||
    const res = await getVideo(server.url, video1Server2UUID)
 | 
			
		||||
    {
 | 
			
		||||
      const res = await getVideo(server.url, video1Server2UUID)
 | 
			
		||||
 | 
			
		||||
    const video: VideoDetails = res.body
 | 
			
		||||
      const video: VideoDetails = res.body
 | 
			
		||||
 | 
			
		||||
    for (const file of video.files) {
 | 
			
		||||
      checkMagnetWebseeds(file, webseeds)
 | 
			
		||||
      for (const file of video.files) {
 | 
			
		||||
        checkMagnetWebseeds(file, webseeds)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -133,6 +153,20 @@ async function check2Webseeds () {
 | 
			
		|||
  for (const resolution of [ 240, 360, 480, 720 ]) {
 | 
			
		||||
    expect(files.find(f => f === `${video1Server2UUID}-${resolution}.mp4`)).to.not.be.undefined
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    const res = await getStats(servers[0].url)
 | 
			
		||||
    const data: ServerStats = res.body
 | 
			
		||||
 | 
			
		||||
    expect(data.videosRedundancy).to.have.lengthOf(1)
 | 
			
		||||
    const stat = data.videosRedundancy[0]
 | 
			
		||||
 | 
			
		||||
    expect(stat.strategy).to.equal(strategy)
 | 
			
		||||
    expect(stat.totalSize).to.equal(102400)
 | 
			
		||||
    expect(stat.totalUsed).to.be.at.least(1).and.below(102401)
 | 
			
		||||
    expect(stat.totalVideoFiles).to.equal(4)
 | 
			
		||||
    expect(stat.totalVideos).to.equal(1)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function cleanServers () {
 | 
			
		||||
| 
						 | 
				
			
			@ -142,15 +176,16 @@ async function cleanServers () {
 | 
			
		|||
describe('Test videos redundancy', function () {
 | 
			
		||||
 | 
			
		||||
  describe('With most-views strategy', function () {
 | 
			
		||||
    const strategy = 'most-views'
 | 
			
		||||
 | 
			
		||||
    before(function () {
 | 
			
		||||
      this.timeout(120000)
 | 
			
		||||
 | 
			
		||||
      return runServers('most-views')
 | 
			
		||||
      return runServers(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('Should have 1 webseed on the first video', function () {
 | 
			
		||||
      return check1WebSeed()
 | 
			
		||||
      return check1WebSeed(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('Should enable redundancy on server 1', function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +195,7 @@ describe('Test videos redundancy', function () {
 | 
			
		|||
    it('Should have 2 webseed on the first video', function () {
 | 
			
		||||
      this.timeout(40000)
 | 
			
		||||
 | 
			
		||||
      return check2Webseeds()
 | 
			
		||||
      return check2Webseeds(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    after(function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -169,15 +204,16 @@ describe('Test videos redundancy', function () {
 | 
			
		|||
  })
 | 
			
		||||
 | 
			
		||||
  describe('With trending strategy', function () {
 | 
			
		||||
    const strategy = 'trending'
 | 
			
		||||
 | 
			
		||||
    before(function () {
 | 
			
		||||
      this.timeout(120000)
 | 
			
		||||
 | 
			
		||||
      return runServers('trending')
 | 
			
		||||
      return runServers(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('Should have 1 webseed on the first video', function () {
 | 
			
		||||
      return check1WebSeed()
 | 
			
		||||
      return check1WebSeed(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('Should enable redundancy on server 1', function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -187,7 +223,7 @@ describe('Test videos redundancy', function () {
 | 
			
		|||
    it('Should have 2 webseed on the first video', function () {
 | 
			
		||||
      this.timeout(40000)
 | 
			
		||||
 | 
			
		||||
      return check2Webseeds()
 | 
			
		||||
      return check2Webseeds(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    after(function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -196,15 +232,16 @@ describe('Test videos redundancy', function () {
 | 
			
		|||
  })
 | 
			
		||||
 | 
			
		||||
  describe('With recently added strategy', function () {
 | 
			
		||||
    const strategy = 'recently-added'
 | 
			
		||||
 | 
			
		||||
    before(function () {
 | 
			
		||||
      this.timeout(120000)
 | 
			
		||||
 | 
			
		||||
      return runServers('recently-added', { minViews: 3 })
 | 
			
		||||
      return runServers(strategy, { minViews: 3 })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('Should have 1 webseed on the first video', function () {
 | 
			
		||||
      return check1WebSeed()
 | 
			
		||||
      return check1WebSeed(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('Should enable redundancy on server 1', function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -218,7 +255,7 @@ describe('Test videos redundancy', function () {
 | 
			
		|||
      await wait(15000)
 | 
			
		||||
      await waitJobs(servers)
 | 
			
		||||
 | 
			
		||||
      return check1WebSeed()
 | 
			
		||||
      return check1WebSeed(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it('Should view 2 times the first video', async function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +271,7 @@ describe('Test videos redundancy', function () {
 | 
			
		|||
    it('Should have 2 webseed on the first video', function () {
 | 
			
		||||
      this.timeout(40000)
 | 
			
		||||
 | 
			
		||||
      return check2Webseeds()
 | 
			
		||||
      return check2Webseeds(strategy)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    after(function () {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ import { waitJobs } from '../../utils/server/jobs'
 | 
			
		|||
 | 
			
		||||
const expect = chai.expect
 | 
			
		||||
 | 
			
		||||
describe('Test stats', function () {
 | 
			
		||||
describe('Test stats (excluding redundancy)', function () {
 | 
			
		||||
  let servers: ServerInfo[] = []
 | 
			
		||||
 | 
			
		||||
  before(async function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +65,7 @@ describe('Test stats', function () {
 | 
			
		|||
    expect(data.totalVideos).to.equal(1)
 | 
			
		||||
    expect(data.totalInstanceFollowers).to.equal(2)
 | 
			
		||||
    expect(data.totalInstanceFollowing).to.equal(1)
 | 
			
		||||
    expect(data.videosRedundancy).to.have.lengthOf(0)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('Should have the correct stats on instance 2', async function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +80,7 @@ describe('Test stats', function () {
 | 
			
		|||
    expect(data.totalVideos).to.equal(1)
 | 
			
		||||
    expect(data.totalInstanceFollowers).to.equal(1)
 | 
			
		||||
    expect(data.totalInstanceFollowing).to.equal(1)
 | 
			
		||||
    expect(data.videosRedundancy).to.have.lengthOf(0)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('Should have the correct stats on instance 3', async function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +95,7 @@ describe('Test stats', function () {
 | 
			
		|||
    expect(data.totalVideos).to.equal(1)
 | 
			
		||||
    expect(data.totalInstanceFollowing).to.equal(1)
 | 
			
		||||
    expect(data.totalInstanceFollowers).to.equal(0)
 | 
			
		||||
    expect(data.videosRedundancy).to.have.lengthOf(0)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  after(async function () {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,16 @@
 | 
			
		|||
import { makeGetRequest } from '../'
 | 
			
		||||
 | 
			
		||||
function getStats (url: string) {
 | 
			
		||||
function getStats (url: string, useCache = false) {
 | 
			
		||||
  const path = '/api/v1/server/stats'
 | 
			
		||||
 | 
			
		||||
  const query = {
 | 
			
		||||
    t: useCache ? undefined : new Date().getTime()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return makeGetRequest({
 | 
			
		||||
    url,
 | 
			
		||||
    path,
 | 
			
		||||
    query,
 | 
			
		||||
    statusCodeExpected: 200
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import { VideoRedundancyStrategy } from '../redundancy'
 | 
			
		||||
 | 
			
		||||
export interface ServerStats {
 | 
			
		||||
  totalUsers: number
 | 
			
		||||
  totalLocalVideos: number
 | 
			
		||||
| 
						 | 
				
			
			@ -9,4 +11,12 @@ export interface ServerStats {
 | 
			
		|||
 | 
			
		||||
  totalInstanceFollowers: number
 | 
			
		||||
  totalInstanceFollowing: number
 | 
			
		||||
 | 
			
		||||
  videosRedundancy: {
 | 
			
		||||
    strategy: VideoRedundancyStrategy
 | 
			
		||||
    totalSize: number
 | 
			
		||||
    totalUsed: number
 | 
			
		||||
    totalVideoFiles: number
 | 
			
		||||
    totalVideos: number
 | 
			
		||||
  }[]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue