]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add redundancy stats
authorChocobozzz <me@florianbigard.com>
Fri, 14 Sep 2018 12:57:59 +0000 (14:57 +0200)
committerChocobozzz <me@florianbigard.com>
Fri, 14 Sep 2018 12:57:59 +0000 (14:57 +0200)
server/controllers/api/overviews.ts
server/controllers/api/server/stats.ts
server/initializers/constants.ts
server/models/redundancy/video-redundancy.ts
server/tests/api/server/redundancy.ts
server/tests/api/server/stats.ts
server/tests/utils/server/stats.ts
shared/models/server/server-stats.model.ts

index cc3cc54a79e4d02b03bb8ec0c2bc83107e0aa6cb..8b67730565d99224d68d290e3b0e99ad24e4a18f 100644 (file)
@@ -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 ] })
 
index 6f4fe938c3c99a31d63641db029270ae0d9f9088..bb6311e81cd40db32961b7f0cbe73929d556562a 100644 (file)
@@ -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()
index 9cccb0919aded1510c7ef1b3fbef372bae37e7c9..e8dab21db4e6857b842e17f19d34d9158f146184 100644 (file)
@@ -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'
 }
 
 // ---------------------------------------------------------------------------
index b7454c61761b351665ae2fa690628ffdb151e309..6ae02efb988987af002f28039042b1f21f7b1276 100644 (file)
@@ -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,
index 6574e8ea9dbb732b7451b5fa72cc6cae38c6935c..c0ab251e600a0e9fcc99af9248fb4da25c8fb05a 100644 (file)
@@ -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 () {
index fc9b8880569af18391ed4e9afda6f9f412cc76c2..d8a3268bb969d933cd253a580734c8f1f0b05257 100644 (file)
@@ -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 () {
index 9cdec6cff490f8f32e7e73b55f0d99c85e10e929..01989d952b8c460fc811fef0f1abd4c68e159207 100644 (file)
@@ -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
   })
 }
index 5c1bf34689999d584673770af34b93e092e7b772..a6bd2d4d35ee2756ba05c2e535f83233bbc629ce 100644 (file)
@@ -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
+  }[]
 }